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;
//
- 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';