Skip to content

Commit b0b330f

Browse files
mohammedzamakhanmgechev
authored andcommitted
feat(rule): click event should be accompanied with key event (#761)
1 parent 77a5e32 commit b0b330f

File tree

3 files changed

+101
-0
lines changed

3 files changed

+101
-0
lines changed

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export { Rule as TemplateNoAnyRule } from './templateNoAnyRule';
3333
export { Rule as TemplateAccessibilityLabelForVisitor } from './templateAccessibilityLabelForRule';
3434
export { Rule as TemplateAccessibilityValidAriaRule } from './templateAccessibilityValidAriaRule';
3535
export { Rule as TemplatesAccessibilityAnchorContentRule } from './templateAccessibilityAnchorContentRule';
36+
export { Rule as TemplateClickEventsHaveKeyEventsRule } from './templateClickEventsHaveKeyEventsRule';
3637
export { Rule as TemplateAccessibilityAltTextRule } from './templateAccessibilityAltTextRule';
3738
export { Rule as TemplatesNoNegatedAsync } from './templatesNoNegatedAsyncRule';
3839
export { Rule as TemplateNoAutofocusRule } from './templateNoAutofocusRule';
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { ElementAst } from '@angular/compiler';
2+
import { IRuleMetadata, RuleFailure, Rules } from 'tslint/lib';
3+
import { SourceFile } from 'typescript/lib/typescript';
4+
import { NgWalker } from './angular/ngWalker';
5+
import { BasicTemplateAstVisitor } from './angular/templates/basicTemplateAstVisitor';
6+
7+
export class Rule extends Rules.AbstractRule {
8+
static readonly metadata: IRuleMetadata = {
9+
description: 'Ensures that the click event is accompanied with at least one key event keyup, keydown or keypress',
10+
options: null,
11+
optionsDescription: 'Not configurable.',
12+
rationale: 'Keyboard is important for users with physical disabilities who cannot use mouse.',
13+
ruleName: 'template-click-events-have-key-events',
14+
type: 'functionality',
15+
typescriptOnly: true
16+
};
17+
18+
static readonly FAILURE_STRING = 'click must be accompanied by either keyup, keydown or keypress event for accessibility';
19+
20+
apply(sourceFile: SourceFile): RuleFailure[] {
21+
return this.applyWithWalker(
22+
new NgWalker(sourceFile, this.getOptions(), {
23+
templateVisitorCtrl: TemplateClickEventsHaveKeyEventsVisitor
24+
})
25+
);
26+
}
27+
}
28+
29+
class TemplateClickEventsHaveKeyEventsVisitor extends BasicTemplateAstVisitor {
30+
visitElement(el: ElementAst, context: any) {
31+
this.validateElement(el);
32+
super.visitElement(el, context);
33+
}
34+
35+
private validateElement(el: ElementAst): void {
36+
const hasClick = el.outputs.some(output => output.name === 'click');
37+
if (!hasClick) {
38+
return;
39+
}
40+
const hasKeyEvent = el.outputs.some(output => output.name === 'keyup' || output.name === 'keydown' || output.name === 'keypress');
41+
42+
if (hasKeyEvent) {
43+
return;
44+
}
45+
46+
const {
47+
sourceSpan: {
48+
end: { offset: endOffset },
49+
start: { offset: startOffset }
50+
}
51+
} = el;
52+
53+
this.addFailureFromStartToEnd(startOffset, endOffset, Rule.FAILURE_STRING);
54+
}
55+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Rule } from '../src/templateClickEventsHaveKeyEventsRule';
2+
import { assertAnnotated, assertSuccess } from './testHelper';
3+
4+
const {
5+
FAILURE_STRING,
6+
metadata: { ruleName }
7+
} = Rule;
8+
9+
describe(ruleName, () => {
10+
describe('failure', () => {
11+
it('should fail when click is not accompanied with key events', () => {
12+
const source = `
13+
@Component({
14+
template: \`
15+
<div (click)="onClick()"></div>
16+
~~~~~~~~~~~~~~~~~~~~~~~~~
17+
\`
18+
})
19+
class Bar {}
20+
`;
21+
assertAnnotated({
22+
message: FAILURE_STRING,
23+
ruleName,
24+
source
25+
});
26+
});
27+
});
28+
29+
describe('success', () => {
30+
it('should work find when click events are associated with key events', () => {
31+
const source = `
32+
@Component({
33+
template: \`
34+
<div (click)="onClick()" (keyup)="onKeyup()"></div>
35+
<div (click)="onClick()" (keydown)="onKeyDown()"></div>
36+
<div (click)="onClick()" (keypress)="onKeyPress()"></div>
37+
<div (click)="onClick()" (keyup)="onKeyup()" (keydown)="onKeyDown()" (keypress)="onKeyPress()"></div>
38+
\`
39+
})
40+
class Bar {}
41+
`;
42+
assertSuccess(ruleName, source);
43+
});
44+
});
45+
});

0 commit comments

Comments
 (0)