Skip to content
This repository has been archived by the owner on Jul 15, 2023. It is now read-only.

Fix #276: Add check for title attribute for images. #540

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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