From 865ec3b2e18685154a2e7ff5c983e5eca6912cc1 Mon Sep 17 00:00:00 2001 From: Zama Khan Mohammed Date: Wed, 27 Feb 2019 13:12:26 -0600 Subject: [PATCH] feat(rule): heading and anchor elements should have content (#762) --- src/index.ts | 1 - ...mplateAccessibilityElementsContentRule.ts} | 24 ++++-- ...lateAccessibilityAnchorContentRule.spec.ts | 45 ---------- ...teAccessibilityElementsContentRule.spec.ts | 82 +++++++++++++++++++ 4 files changed, 97 insertions(+), 55 deletions(-) rename src/{templateAccessibilityAnchorContentRule.ts => templateAccessibilityElementsContentRule.ts} (54%) delete mode 100644 test/templateAccessibilityAnchorContentRule.spec.ts create mode 100644 test/templateAccessibilityElementsContentRule.spec.ts diff --git a/src/index.ts b/src/index.ts index 09f7a2b60..bcec4b20a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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'; diff --git a/src/templateAccessibilityAnchorContentRule.ts b/src/templateAccessibilityElementsContentRule.ts similarity index 54% rename from src/templateAccessibilityAnchorContentRule.ts rename to src/templateAccessibilityElementsContentRule.ts index 704ed5c5c..4435977c7 100644 --- a/src/templateAccessibilityAnchorContentRule.ts +++ b/src/templateAccessibilityElementsContentRule.ts @@ -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; } @@ -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 }) ); } diff --git a/test/templateAccessibilityAnchorContentRule.spec.ts b/test/templateAccessibilityAnchorContentRule.spec.ts deleted file mode 100644 index 32ee73d5a..000000000 --- a/test/templateAccessibilityAnchorContentRule.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Rule } from '../src/templateAccessibilityAnchorContentRule'; -import { assertAnnotated, assertSuccess } from './testHelper'; - -const { - FAILURE_MESSAGE, - metadata: { ruleName } -} = Rule; - -describe(ruleName, () => { - describe('failure', () => { - it('should fail with no content in anchor tag', () => { - const source = ` - @Component({ - template: \` - - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - \` - }) - class Bar {} - `; - assertAnnotated({ - message: FAILURE_MESSAGE, - ruleName, - source - }); - }); - }); - - describe('success', () => { - it('should work when anchor has any kind of content in it', () => { - const source = ` - @Component({ - template: \` - Anchor Content! - - - - \` - }) - class Bar {} - `; - assertSuccess(ruleName, source); - }); - }); -}); diff --git a/test/templateAccessibilityElementsContentRule.spec.ts b/test/templateAccessibilityElementsContentRule.spec.ts new file mode 100644 index 000000000..d54b90589 --- /dev/null +++ b/test/templateAccessibilityElementsContentRule.spec.ts @@ -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: \` +

+ ~~~~~~~~~~~~~~~~~~~ + \` + }) + class Bar {} + `; + assertAnnotated({ + message: getErrorMessage('h1'), + ruleName, + source + }); + }); + + it('should fail with no content in anchor tag', () => { + const source = ` + @Component({ + template: \` + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + \` + }) + class Bar {} + `; + assertAnnotated({ + message: getErrorMessage('a'), + ruleName, + source + }); + }); + + it('should fail with no content in anchor tag', () => { + const source = ` + @Component({ + template: \` + + ~~~~~~~~ + \` + }) + 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: \` +

Heading Content!

+

+

+

+ Anchor Content! + + + + \` + }) + class Bar {} + `; + assertSuccess(ruleName, source); + }); + }); +});