Skip to content

Commit

Permalink
feat(rule): heading and anchor elements should have content (#762)
Browse files Browse the repository at this point in the history
  • Loading branch information
mohammedzamakhan authored and mgechev committed Feb 27, 2019
1 parent bbf7a32 commit 865ec3b
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 55 deletions.
1 change: 0 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ export { Rule as TemplateNoCallExpressionRule } from './templateNoCallExpression
export { Rule as TemplateNoDistractingElementsRule } from './templateNoDistractingElementsRule';
export { Rule as TemplateNoNegatedAsyncRule } from './templateNoNegatedAsyncRule';
export { Rule as TemplateUseTrackByFunctionRule } from './templateUseTrackByFunctionRule';
export { Rule as TemplatesAccessibilityAnchorContentRule } from './templateAccessibilityAnchorContentRule';
export { Rule as UseComponentSelectorRule } from './useComponentSelectorRule';
export { Rule as UseComponentViewEncapsulationRule } from './useComponentViewEncapsulationRule';
export { Rule as UseLifecycleInterfaceRule } from './useLifecycleInterfaceRule';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { ElementAst } from '@angular/compiler';
import { IRuleMetadata, RuleFailure, Rules, Utils } from 'tslint/lib';
import { IRuleMetadata, RuleFailure, Rules } from 'tslint/lib';
import { SourceFile } from 'typescript';
import { sprintf } from 'sprintf-js';
import { NgWalker } from './angular/ngWalker';
import { BasicTemplateAstVisitor } from './angular';

class TemplateAccessibilityAnchorContentVisitor extends BasicTemplateAstVisitor {
class TemplateAccessibilityElementsContentVisitor extends BasicTemplateAstVisitor {
visitElement(ast: ElementAst, context: any) {
this.validateElement(ast);
super.visitElement(ast, context);
}

validateElement(element: ElementAst) {
if (element.name !== 'a') {
if (Rule.ELEMENTS.indexOf(element.name) === -1) {
return;
}

Expand All @@ -26,27 +27,32 @@ class TemplateAccessibilityAnchorContentVisitor extends BasicTemplateAstVisitor
start: { offset: startOffset }
}
} = element;
this.addFailureFromStartToEnd(startOffset, endOffset, Rule.FAILURE_MESSAGE);
this.addFailureFromStartToEnd(startOffset, endOffset, getErrorMessage(element.name));
}
}

export const getErrorMessage = (element: string): string => {
return sprintf(Rule.FAILURE_STRING, element);
};

export class Rule extends Rules.AbstractRule {
static readonly metadata: IRuleMetadata = {
description: 'Ensures that the anchor element has some content in it',
description: 'Ensures that the heading, anchor and button elements have content in it',
options: null,
optionsDescription: 'Not configurable.',
rationale: 'Anchor elements should have content to be accessible by screen readers',
ruleName: 'template-accessibility-anchor-content',
rationale: 'Heading, anchor and button elements should have content to be accessible by screen readers',
ruleName: 'template-accessibility-elements-content',
type: 'functionality',
typescriptOnly: true
};

static readonly FAILURE_MESSAGE = 'Anchor element should have content';
static readonly FAILURE_STRING = '<%s/> element should have content';
static readonly ELEMENTS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'a', 'button'];

apply(sourceFile: SourceFile): RuleFailure[] {
return this.applyWithWalker(
new NgWalker(sourceFile, this.getOptions(), {
templateVisitorCtrl: TemplateAccessibilityAnchorContentVisitor
templateVisitorCtrl: TemplateAccessibilityElementsContentVisitor
})
);
}
Expand Down
45 changes: 0 additions & 45 deletions test/templateAccessibilityAnchorContentRule.spec.ts

This file was deleted.

82 changes: 82 additions & 0 deletions test/templateAccessibilityElementsContentRule.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { getErrorMessage, Rule } from '../src/templateAccessibilityElementsContentRule';
import { assertAnnotated, assertSuccess } from './testHelper';

const {
metadata: { ruleName }
} = Rule;

describe(ruleName, () => {
describe('failure', () => {
it('should fail with no content in heading tag', () => {
const source = `
@Component({
template: \`
<h1 class="size-1"></h1>
~~~~~~~~~~~~~~~~~~~
\`
})
class Bar {}
`;
assertAnnotated({
message: getErrorMessage('h1'),
ruleName,
source
});
});

it('should fail with no content in anchor tag', () => {
const source = `
@Component({
template: \`
<a href="#" [routerLink]="['route1']"></a>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
\`
})
class Bar {}
`;
assertAnnotated({
message: getErrorMessage('a'),
ruleName,
source
});
});

it('should fail with no content in anchor tag', () => {
const source = `
@Component({
template: \`
<button></button>
~~~~~~~~
\`
})
class Bar {}
`;
assertAnnotated({
message: getErrorMessage('button'),
ruleName,
source
});
});
});

describe('success', () => {
it('should work when anchor or headings has any kind of content in it', () => {
const source = `
@Component({
template: \`
<h1>Heading Content!</h1>
<h2><app-content></app-content></h2>
<h3 [innerHTML]="dangerouslySetHTML"></h3>
<h4 [innerText]="text"></h4>
<a>Anchor Content!</a>
<a><app-content></app-content></a>
<a [innerHTML]="dangerouslySetHTML"></a>
<a [innerText]="text"></a>
\`
})
class Bar {}
`;
assertSuccess(ruleName, source);
});
});
});

0 comments on commit 865ec3b

Please sign in to comment.