Skip to content

Commit

Permalink
Fix microsoft#276: Add check for title attribute for images.
Browse files Browse the repository at this point in the history
* All images must have alt attribute.
* If the image is meant to be ignored, have a presentation role with an
empty alt attribute.
* If the image has an empty alt attribute but has a title attribute with
a value, it fails.
  • Loading branch information
afifsohaili committed Oct 10, 2018
1 parent 1706244 commit 22762d1
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 4 deletions.
18 changes: 16 additions & 2 deletions src/reactA11yImgHasAltRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand All @@ -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.
*/
Expand Down Expand Up @@ -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;

// <img alt='altValue' role='presentation' />
if (!isEmptyAlt && isPresentationRole && !allowNonEmptyAltWithRolePresentation) {
if (!isEmptyAlt && isPresentationRole && !allowNonEmptyAltWithRolePresentation && !titleAttribute) {
this.addFailureAt(
node.getStart(),
node.getWidth(),
getFailureStringNonEmptyAltAndPresentationRole(tagName)
);
} else if (isEmptyAlt && !isPresentationRole) { // <img alt='' />
} else if (isEmptyAlt && !isPresentationRole && !titleAttribute) { // <img alt='' />
this.addFailureAt(
node.getStart(),
node.getWidth(),
getFailureStringEmptyAltAndNotPresentationRole(tagName)
);
} else if (isEmptyAlt && titleAttribute && !isEmptyTitle) {
this.addFailureAt(
node.getStart(),
node.getWidth(),
getFailureStringEmptyAltAndNotEmptyTitle(tagName)
);
}
}
}
Expand Down
88 changes: 86 additions & 2 deletions src/tests/reactA11yImgHasAltRuleTests.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { TestHelper } from './TestHelper';
import {
getFailureStringEmptyAltAndNotEmptyTitle,
getFailureStringEmptyAltAndNotPresentationRole,
getFailureStringNoAlt,
getFailureStringNonEmptyAltAndPresentationRole,
getFailureStringEmptyAltAndNotPresentationRole
getFailureStringNonEmptyAltAndPresentationRole
} from '../reactA11yImgHasAltRule';

/**
Expand Down Expand Up @@ -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 = <img role='presentation' alt title/>;
const b = <img role='presentation' alt='' title=''/>;
const c = <img role='button presentation' alt={ undefined } title={ undefined }/>;
const d = <img role={'presentation button'} alt={''} title={''}/>;
`;
TestHelper.assertNoViolation(ruleName, fileName);
});
});

describe('should fail', () => {
Expand Down Expand Up @@ -158,6 +171,40 @@ const d = <img alt={''} /> `;
]
);
});

it('when the img tag has empty attribute alt but not-empty attribute title', () => {
const fileName: string = `import React = require('react');
const a = <img alt='' title='Some image' role='presentation' />;
const b = <img alt title='Some image' role='presentation' />;
const c = <img alt={''} title='Some image' role='presentation' />;
`;

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')
},
]
);
});
});
});

Expand Down Expand Up @@ -197,6 +244,43 @@ const d = <img alt={''} /> `;
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 = <Picture alt='' title='Some image' role='presentation' />;
const b = <Picture alt title='Some image' role='presentation' />;
const c = <Picture alt={''} title='Some image' role='presentation' />;
`;

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

Expand Down

0 comments on commit 22762d1

Please sign in to comment.