diff --git a/src/reactA11yImgHasAltRule.ts b/src/reactA11yImgHasAltRule.ts index 7cd5598bc..3a1b200d9 100644 --- a/src/reactA11yImgHasAltRule.ts +++ b/src/reactA11yImgHasAltRule.ts @@ -12,6 +12,7 @@ import { isJsxSpreadAttribute } from './utils/TypeGuard'; const ROLE_STRING: string = 'role'; const ALT_STRING: string = 'alt'; +const TITLE_STRING: string = 'title'; export function getFailureStringNoAlt(tagName: string): string { return `<${tagName}> elements must have an non-empty alt attribute or \ @@ -29,6 +30,11 @@ export function getFailureStringNonEmptyAltAndPresentationRole(tagName: string): Remove role='presentation' or specify 'alt' attribute to be empty when role attributes equals 'presentation'.`; } +export function getFailureStringEmptyAltAndNotEmptyTitle(tagName: string): string { + return `The value of alt attribute in <${tagName}> tag is empty and the role is presentation, but the value of \ +its title attribute is not empty. Remove the title attribute.`; +} + /** * Enforces that img elements have alt text. */ @@ -101,25 +107,33 @@ class ImgHasAltWalker extends Lint.RuleWalker { } else { const roleAttribute: ts.JsxAttribute = attributes[ROLE_STRING]; const roleAttributeValue = roleAttribute ? getStringLiteral(roleAttribute) : ''; + const titleAttribute: ts.JsxAttribute = attributes[TITLE_STRING]; const isPresentationRole: boolean = !!String(roleAttributeValue).toLowerCase().match(/\bpresentation\b/); const isEmptyAlt: boolean = isEmpty(altAttribute) || getStringLiteral(altAttribute) === ''; + const isEmptyTitle: boolean = isEmpty(titleAttribute) || getStringLiteral(titleAttribute) === ''; const allowNonEmptyAltWithRolePresentation: boolean = options.length > 1 ? options[1].allowNonEmptyAltWithRolePresentation : false; // altValue - if (!isEmptyAlt && isPresentationRole && !allowNonEmptyAltWithRolePresentation) { + if (!isEmptyAlt && isPresentationRole && !allowNonEmptyAltWithRolePresentation && !titleAttribute) { this.addFailureAt( node.getStart(), node.getWidth(), getFailureStringNonEmptyAltAndPresentationRole(tagName) ); - } else if (isEmptyAlt && !isPresentationRole) { // + } else if (isEmptyAlt && !isPresentationRole && !titleAttribute) { // this.addFailureAt( node.getStart(), node.getWidth(), getFailureStringEmptyAltAndNotPresentationRole(tagName) ); + } else if (isEmptyAlt && titleAttribute && !isEmptyTitle) { + this.addFailureAt( + node.getStart(), + node.getWidth(), + getFailureStringEmptyAltAndNotEmptyTitle(tagName) + ); } } } diff --git a/src/tests/reactA11yImgHasAltRuleTests.ts b/src/tests/reactA11yImgHasAltRuleTests.ts index 454cbc2ac..23a0e1e75 100644 --- a/src/tests/reactA11yImgHasAltRuleTests.ts +++ b/src/tests/reactA11yImgHasAltRuleTests.ts @@ -1,8 +1,9 @@ import { TestHelper } from './TestHelper'; import { + getFailureStringEmptyAltAndNotEmptyTitle, + getFailureStringEmptyAltAndNotPresentationRole, getFailureStringNoAlt, - getFailureStringNonEmptyAltAndPresentationRole, - getFailureStringEmptyAltAndNotPresentationRole + getFailureStringNonEmptyAltAndPresentationRole } from '../reactA11yImgHasAltRule'; /** @@ -56,6 +57,18 @@ describe('reactA11yImgHasAlt', () => { const ruleOptions: any[] = [[], { allowNonEmptyAltWithRolePresentation: true }]; TestHelper.assertNoViolationWithOptions(ruleName, ruleOptions, fileName); }); + + it('when the img element has empty alt value, empty title, and presentation role', () => { + const fileName: string = ` + import React = require('react'); + + const a = ; + const b = ; + const c = {; + const d = {''}; + `; + TestHelper.assertNoViolation(ruleName, fileName); + }); }); describe('should fail', () => { @@ -158,6 +171,40 @@ const d = {''} `; ] ); }); + + it('when the img tag has empty attribute alt but not-empty attribute title', () => { + const fileName: string = `import React = require('react'); + +const a = ; +const b = ; +const c = {''}; +`; + + TestHelper.assertViolations( + ruleName, + fileName, + [ + { + name: 'file.tsx', + ruleName: ruleName, + startPosition: { character: 11, line: 3 }, + failure: getFailureStringEmptyAltAndNotEmptyTitle('img') + }, + { + name: 'file.tsx', + ruleName: ruleName, + startPosition: { character: 11, line: 4 }, + failure: getFailureStringEmptyAltAndNotEmptyTitle('img') + }, + { + name: 'file.tsx', + ruleName: ruleName, + startPosition: { character: 11, line: 5 }, + failure: getFailureStringEmptyAltAndNotEmptyTitle('img') + }, + ] + ); + }); }); }); @@ -197,6 +244,43 @@ const d = {''} `; describe('should fail', () => { const fileDirectory: string = 'test-data/a11yImgHasAlt/CustomElementTests/FailingTestInputs/'; + it('when the custom element has empty attribute alt but not-empty attribute title', () => { + const fileName: string = `import React = require('react'); + +let Picture; + +const a = ; +const b = ; +const c = ; +`; + + TestHelper.assertViolationsWithOptions( + ruleName, + options, + fileName, + [ + { + name: 'file.tsx', + ruleName: ruleName, + startPosition: { character: 11, line: 5 }, + failure: getFailureStringEmptyAltAndNotEmptyTitle('Picture') + }, + { + name: 'file.tsx', + ruleName: ruleName, + startPosition: { character: 11, line: 6 }, + failure: getFailureStringEmptyAltAndNotEmptyTitle('Picture') + }, + { + name: 'file.tsx', + ruleName: ruleName, + startPosition: { character: 11, line: 7 }, + failure: getFailureStringEmptyAltAndNotEmptyTitle('Picture') + }, + ] + ); + }); + it('when custom element or img has no alt prop', () => { const fileName: string = fileDirectory + 'CustomElementHasNoAltProp.tsx';