Skip to content

Commit 865ec3b

Browse files
mohammedzamakhanmgechev
authored andcommitted
feat(rule): heading and anchor elements should have content (#762)
1 parent bbf7a32 commit 865ec3b

File tree

4 files changed

+97
-55
lines changed

4 files changed

+97
-55
lines changed

src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ export { Rule as TemplateNoCallExpressionRule } from './templateNoCallExpression
4444
export { Rule as TemplateNoDistractingElementsRule } from './templateNoDistractingElementsRule';
4545
export { Rule as TemplateNoNegatedAsyncRule } from './templateNoNegatedAsyncRule';
4646
export { Rule as TemplateUseTrackByFunctionRule } from './templateUseTrackByFunctionRule';
47-
export { Rule as TemplatesAccessibilityAnchorContentRule } from './templateAccessibilityAnchorContentRule';
4847
export { Rule as UseComponentSelectorRule } from './useComponentSelectorRule';
4948
export { Rule as UseComponentViewEncapsulationRule } from './useComponentViewEncapsulationRule';
5049
export { Rule as UseLifecycleInterfaceRule } from './useLifecycleInterfaceRule';

src/templateAccessibilityAnchorContentRule.ts renamed to src/templateAccessibilityElementsContentRule.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import { ElementAst } from '@angular/compiler';
2-
import { IRuleMetadata, RuleFailure, Rules, Utils } from 'tslint/lib';
2+
import { IRuleMetadata, RuleFailure, Rules } from 'tslint/lib';
33
import { SourceFile } from 'typescript';
4+
import { sprintf } from 'sprintf-js';
45
import { NgWalker } from './angular/ngWalker';
56
import { BasicTemplateAstVisitor } from './angular';
67

7-
class TemplateAccessibilityAnchorContentVisitor extends BasicTemplateAstVisitor {
8+
class TemplateAccessibilityElementsContentVisitor extends BasicTemplateAstVisitor {
89
visitElement(ast: ElementAst, context: any) {
910
this.validateElement(ast);
1011
super.visitElement(ast, context);
1112
}
1213

1314
validateElement(element: ElementAst) {
14-
if (element.name !== 'a') {
15+
if (Rule.ELEMENTS.indexOf(element.name) === -1) {
1516
return;
1617
}
1718

@@ -26,27 +27,32 @@ class TemplateAccessibilityAnchorContentVisitor extends BasicTemplateAstVisitor
2627
start: { offset: startOffset }
2728
}
2829
} = element;
29-
this.addFailureFromStartToEnd(startOffset, endOffset, Rule.FAILURE_MESSAGE);
30+
this.addFailureFromStartToEnd(startOffset, endOffset, getErrorMessage(element.name));
3031
}
3132
}
3233

34+
export const getErrorMessage = (element: string): string => {
35+
return sprintf(Rule.FAILURE_STRING, element);
36+
};
37+
3338
export class Rule extends Rules.AbstractRule {
3439
static readonly metadata: IRuleMetadata = {
35-
description: 'Ensures that the anchor element has some content in it',
40+
description: 'Ensures that the heading, anchor and button elements have content in it',
3641
options: null,
3742
optionsDescription: 'Not configurable.',
38-
rationale: 'Anchor elements should have content to be accessible by screen readers',
39-
ruleName: 'template-accessibility-anchor-content',
43+
rationale: 'Heading, anchor and button elements should have content to be accessible by screen readers',
44+
ruleName: 'template-accessibility-elements-content',
4045
type: 'functionality',
4146
typescriptOnly: true
4247
};
4348

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

4652
apply(sourceFile: SourceFile): RuleFailure[] {
4753
return this.applyWithWalker(
4854
new NgWalker(sourceFile, this.getOptions(), {
49-
templateVisitorCtrl: TemplateAccessibilityAnchorContentVisitor
55+
templateVisitorCtrl: TemplateAccessibilityElementsContentVisitor
5056
})
5157
);
5258
}

test/templateAccessibilityAnchorContentRule.spec.ts

Lines changed: 0 additions & 45 deletions
This file was deleted.
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { getErrorMessage, Rule } from '../src/templateAccessibilityElementsContentRule';
2+
import { assertAnnotated, assertSuccess } from './testHelper';
3+
4+
const {
5+
metadata: { ruleName }
6+
} = Rule;
7+
8+
describe(ruleName, () => {
9+
describe('failure', () => {
10+
it('should fail with no content in heading tag', () => {
11+
const source = `
12+
@Component({
13+
template: \`
14+
<h1 class="size-1"></h1>
15+
~~~~~~~~~~~~~~~~~~~
16+
\`
17+
})
18+
class Bar {}
19+
`;
20+
assertAnnotated({
21+
message: getErrorMessage('h1'),
22+
ruleName,
23+
source
24+
});
25+
});
26+
27+
it('should fail with no content in anchor tag', () => {
28+
const source = `
29+
@Component({
30+
template: \`
31+
<a href="#" [routerLink]="['route1']"></a>
32+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
33+
\`
34+
})
35+
class Bar {}
36+
`;
37+
assertAnnotated({
38+
message: getErrorMessage('a'),
39+
ruleName,
40+
source
41+
});
42+
});
43+
44+
it('should fail with no content in anchor tag', () => {
45+
const source = `
46+
@Component({
47+
template: \`
48+
<button></button>
49+
~~~~~~~~
50+
\`
51+
})
52+
class Bar {}
53+
`;
54+
assertAnnotated({
55+
message: getErrorMessage('button'),
56+
ruleName,
57+
source
58+
});
59+
});
60+
});
61+
62+
describe('success', () => {
63+
it('should work when anchor or headings has any kind of content in it', () => {
64+
const source = `
65+
@Component({
66+
template: \`
67+
<h1>Heading Content!</h1>
68+
<h2><app-content></app-content></h2>
69+
<h3 [innerHTML]="dangerouslySetHTML"></h3>
70+
<h4 [innerText]="text"></h4>
71+
<a>Anchor Content!</a>
72+
<a><app-content></app-content></a>
73+
<a [innerHTML]="dangerouslySetHTML"></a>
74+
<a [innerText]="text"></a>
75+
\`
76+
})
77+
class Bar {}
78+
`;
79+
assertSuccess(ruleName, source);
80+
});
81+
});
82+
});

0 commit comments

Comments
 (0)