testaro 18.1.1 → 18.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/aslint/LICENSE +362 -0
- package/aslint/README.md +260 -0
- package/aslint/app/rules/abstract-rule.ts +83 -0
- package/aslint/app/rules/aslint/aria-hidden-false/aria-hidden-false.test.ts +73 -0
- package/aslint/app/rules/aslint/aria-hidden-false/aria-hidden-false.ts +34 -0
- package/aslint/app/rules/aslint/aria-hidden-false/aria-hidden.documentation.md +32 -0
- package/aslint/app/rules/aslint/aria-role-dialog/aria-role-dialog.documentation.md +49 -0
- package/aslint/app/rules/aslint/aria-role-dialog/aria-role-dialog.test.ts +91 -0
- package/aslint/app/rules/aslint/aria-role-dialog/aria-role-dialog.ts +62 -0
- package/aslint/app/rules/aslint/capital-letters-words/capital-letters-words.documentation.md +44 -0
- package/aslint/app/rules/aslint/capital-letters-words/capital-letters-words.test.ts +111 -0
- package/aslint/app/rules/aslint/capital-letters-words/capital-letters-words.ts +120 -0
- package/aslint/app/rules/aslint/content-editable-missing-attributes/content-editable-missing-attributes.docmentation.md +48 -0
- package/aslint/app/rules/aslint/content-editable-missing-attributes/content-editable-missing-attributes.test.ts +67 -0
- package/aslint/app/rules/aslint/content-editable-missing-attributes/content-editable-missing-attributes.ts +63 -0
- package/aslint/app/rules/aslint/contentinfo-landmark-only-one/contentinfo-landmark-only-one.documentation.md +45 -0
- package/aslint/app/rules/aslint/contentinfo-landmark-only-one/contentinfo-landmark-only-one.test.ts +63 -0
- package/aslint/app/rules/aslint/contentinfo-landmark-only-one/contentinfo-landmark-only-one.ts +44 -0
- package/aslint/app/rules/aslint/elements-not-allowed-in-head/elements-not-allowed-in-head.documentation.md +65 -0
- package/aslint/app/rules/aslint/elements-not-allowed-in-head/elements-not-allowed-in-head.test.ts +53 -0
- package/aslint/app/rules/aslint/elements-not-allowed-in-head/elements-not-allowed-in-head.ts +47 -0
- package/aslint/app/rules/aslint/empty-title-attribute/empty-title-attribute.documentation.md +55 -0
- package/aslint/app/rules/aslint/empty-title-attribute/empty-title-attribute.test.ts +80 -0
- package/aslint/app/rules/aslint/empty-title-attribute/empty-title-attribute.ts +58 -0
- package/aslint/app/rules/aslint/flash-content/flash-content.documentation.md +48 -0
- package/aslint/app/rules/aslint/flash-content/flash-content.test.ts +52 -0
- package/aslint/app/rules/aslint/flash-content/flash-content.ts +32 -0
- package/aslint/app/rules/aslint/font-style-italic/font-style-italic.documentation.md +44 -0
- package/aslint/app/rules/aslint/font-style-italic/font-style-italic.test.ts +12 -0
- package/aslint/app/rules/aslint/font-style-italic/font-style-italic.ts +83 -0
- package/aslint/app/rules/aslint/h1-must-be/h1-must-be.documentation.md +46 -0
- package/aslint/app/rules/aslint/h1-must-be/h1-must-be.test.ts +46 -0
- package/aslint/app/rules/aslint/h1-must-be/h1-must-be.ts +36 -0
- package/aslint/app/rules/aslint/headings-sibling-unique/headings-sibling-unique.documentation.md +57 -0
- package/aslint/app/rules/aslint/headings-sibling-unique/headings-sibling-unique.test.ts +52 -0
- package/aslint/app/rules/aslint/headings-sibling-unique/headings-sibling-unique.ts +63 -0
- package/aslint/app/rules/aslint/horizontal-rule/horizontal-rule.documentation.md +39 -0
- package/aslint/app/rules/aslint/horizontal-rule/horizontal-rule.test.ts +66 -0
- package/aslint/app/rules/aslint/horizontal-rule/horizontal-rule.ts +37 -0
- package/aslint/app/rules/aslint/incorrect-technique-for-hiding-content/incorrect-technique-for-hiding-content.documentation.md +36 -0
- package/aslint/app/rules/aslint/incorrect-technique-for-hiding-content/incorrect-technique-for-hiding-content.test.ts +113 -0
- package/aslint/app/rules/aslint/incorrect-technique-for-hiding-content/incorrect-technique-for-hiding-content.ts +103 -0
- package/aslint/app/rules/aslint/invalid-attribute-dir-value/invalid-attribute-dir-value.documentation.md +34 -0
- package/aslint/app/rules/aslint/invalid-attribute-dir-value/invalid-attribute-dir-value.test.ts +82 -0
- package/aslint/app/rules/aslint/invalid-attribute-dir-value/invalid-attribute-dir-value.ts +44 -0
- package/aslint/app/rules/aslint/label-duplicated-content-title/label-duplicated-content-title.documentation.md +40 -0
- package/aslint/app/rules/aslint/label-duplicated-content-title/label-duplicated-content-title.test.ts +48 -0
- package/aslint/app/rules/aslint/label-duplicated-content-title/label-duplicated-content-title.ts +37 -0
- package/aslint/app/rules/aslint/links-language-destination/links-language-destination.test.ts +50 -0
- package/aslint/app/rules/aslint/links-language-destination/links-language-destination.ts +70 -0
- package/aslint/app/rules/aslint/main-element-only-one/main-element-only-one.test.ts +55 -0
- package/aslint/app/rules/aslint/main-element-only-one/main-element-only-one.ts +83 -0
- package/aslint/app/rules/aslint/main-landmark-must-be-top-level/main-landmark-must-be-top-level.test.ts +12 -0
- package/aslint/app/rules/aslint/main-landmark-must-be-top-level/main-landmark-must-be-top-level.ts +73 -0
- package/aslint/app/rules/aslint/minimum-font-size/minimum-font-size.test.ts +12 -0
- package/aslint/app/rules/aslint/minimum-font-size/minimum-font-size.ts +87 -0
- package/aslint/app/rules/aslint/missing-href-on-a/missing-href-on-a.test.ts +48 -0
- package/aslint/app/rules/aslint/missing-href-on-a/missing-href-on-a.ts +40 -0
- package/aslint/app/rules/aslint/misused-aria-on-focusable-element/misused-aria-on-focusable-element.test.ts +12 -0
- package/aslint/app/rules/aslint/misused-aria-on-focusable-element/misused-aria-on-focusable-element.ts +66 -0
- package/aslint/app/rules/aslint/misused-input-attribute/misused-input-attribute.test.ts +12 -0
- package/aslint/app/rules/aslint/misused-input-attribute/misused-input-attribute.ts +134 -0
- package/aslint/app/rules/aslint/misused-required-attribute/misused-required-attribute.test.ts +12 -0
- package/aslint/app/rules/aslint/misused-required-attribute/misused-required-attribute.ts +90 -0
- package/aslint/app/rules/aslint/navigation-landmark-restrictions/navigation-landmark-restrictions.test.ts +12 -0
- package/aslint/app/rules/aslint/navigation-landmark-restrictions/navigation-landmark-restrictions.ts +48 -0
- package/aslint/app/rules/aslint/obsolete-html-attributes/obsolete-html-attributes.test.ts +12 -0
- package/aslint/app/rules/aslint/obsolete-html-attributes/obsolete-html-attributes.ts +148 -0
- package/aslint/app/rules/aslint/obsolete-html-elements/obsolete-html-elements.test.ts +12 -0
- package/aslint/app/rules/aslint/obsolete-html-elements/obsolete-html-elements.ts +66 -0
- package/aslint/app/rules/aslint/outline-zero/outline-zero.test.ts +12 -0
- package/aslint/app/rules/aslint/outline-zero/outline-zero.ts +85 -0
- package/aslint/app/rules/aslint/overlay/overlay.test.ts +122 -0
- package/aslint/app/rules/aslint/overlay/overlay.ts +141 -0
- package/aslint/app/rules/aslint/role-application/role-application.test.ts +48 -0
- package/aslint/app/rules/aslint/role-application/role-application.ts +38 -0
- package/aslint/app/rules/aslint/rtl-content/rtl-content.test.ts +12 -0
- package/aslint/app/rules/aslint/rtl-content/rtl-content.ts +75 -0
- package/aslint/app/rules/aslint/unclear-uri-on-a/unclear-anchor-uri.test.ts +12 -0
- package/aslint/app/rules/aslint/unclear-uri-on-a/unclear-anchor-uri.ts +48 -0
- package/aslint/app/rules/aslint/unsupported-role-on-element/unsupported-role-on-element.test.ts +12 -0
- package/aslint/app/rules/aslint/unsupported-role-on-element/unsupported-role-on-element.ts +63 -0
- package/package.json +1 -1
- package/testaro/headingAmb.js +103 -0
- package/testaro/template.js +78 -0
- package/tests/testaro.js +1 -0
- package/validation/tests/jobs/headingAmb.json +129 -0
- package/validation/tests/targets/headingAmb/index.html +26 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { DomUtility } from '../../../utils/dom';
|
|
2
|
+
import { CATEGORY_TYPE } from '../../../constants/categoryType';
|
|
3
|
+
import { IIssueReport } from '../../../interfaces/rule-issue.interface';
|
|
4
|
+
import { TextUtility } from '../../../utils/text';
|
|
5
|
+
import { TranslateService } from '../../../services/translate';
|
|
6
|
+
import { $severity } from '../../../constants/accessibility';
|
|
7
|
+
import { Config } from '../../../config';
|
|
8
|
+
import { $accessibilityAuditRules } from '../../../constants/accessibility';
|
|
9
|
+
import { AbstractRule, IAbstractRuleConfig } from '../../abstract-rule';
|
|
10
|
+
|
|
11
|
+
export class OutlineZero extends AbstractRule {
|
|
12
|
+
private appConfig: Config = Config.getInstance();
|
|
13
|
+
|
|
14
|
+
protected selector: () => CSSStyleSheet[] = (): CSSStyleSheet[] => {
|
|
15
|
+
return Array.from(document.styleSheets);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
protected ruleConfig: IAbstractRuleConfig = {
|
|
19
|
+
id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.outline_zero),
|
|
20
|
+
links: [
|
|
21
|
+
{
|
|
22
|
+
content: 'F55: Failure of Success Criteria 2.1.1, 2.4.7, and 3.2.1 due to using script to remove focus when focus is received',
|
|
23
|
+
url: 'https://www.w3.org/TR/WCAG20-TECHS/F55.html'
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
recommendations: [],
|
|
27
|
+
severity: $severity.high,
|
|
28
|
+
type: CATEGORY_TYPE.BEST_PRACTICE
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
public validate(styleSheets: CSSStyleSheet[]): void {
|
|
32
|
+
if (styleSheets.length === 0) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const findOutlineZero = (styles: CSSStyleSheet): void => {
|
|
37
|
+
let currentRule: CSSRule;
|
|
38
|
+
let outlineWidthSize: number;
|
|
39
|
+
let selector: string;
|
|
40
|
+
let styleObject: any;
|
|
41
|
+
let isCSSembededInline: string;
|
|
42
|
+
let href: string | null;
|
|
43
|
+
const defaultSizeValues: string[] = ['thin', 'medium', 'thick'];
|
|
44
|
+
|
|
45
|
+
if (styles.hasOwnProperty('rules') === false || styles.href && styles.href.indexOf(this.appConfig.get('cssTitle')) !== -1) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const rules: CSSRuleList = styles.rules;
|
|
50
|
+
const rulesLength: number = rules.length;
|
|
51
|
+
|
|
52
|
+
for (let i: number = 0; i < rulesLength; i += 1) {
|
|
53
|
+
currentRule = rules[i];
|
|
54
|
+
styleObject = (currentRule as CSSStyleRule).style;
|
|
55
|
+
|
|
56
|
+
if (styleObject && styleObject.outlineWidth !== '' && defaultSizeValues.indexOf(styleObject.outlineWidth) === -1) {
|
|
57
|
+
outlineWidthSize = Number(styleObject.outlineWidth.replace(/[^\d.-]/g, ''));
|
|
58
|
+
|
|
59
|
+
if (outlineWidthSize === 0) {
|
|
60
|
+
href = (currentRule as any).parentStyleSheet.href;
|
|
61
|
+
|
|
62
|
+
if (href) {
|
|
63
|
+
isCSSembededInline = TranslateService.instant('outline_zero_css_embeded_1', [href]);
|
|
64
|
+
} else {
|
|
65
|
+
isCSSembededInline = TranslateService.instant('outline_zero_css_embeded_2', [DomUtility.getEscapedOuterHTML((currentRule as any).parentStyleSheet.ownerNode)]);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
selector = currentRule.cssText.replace(/(outline.*?;)/i, '<em>$1</em>');
|
|
69
|
+
const reportMessage: string = TranslateService.instant('outline_zero_report_message', [styleObject.outlineWidth, selector, isCSSembededInline]);
|
|
70
|
+
|
|
71
|
+
const report: IIssueReport = {
|
|
72
|
+
message: reportMessage,
|
|
73
|
+
node: null,
|
|
74
|
+
ruleId: this.ruleConfig.id
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
this.validator.report(report);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
styleSheets.forEach(findOutlineZero);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { DomUtility } from '../../../utils/dom';
|
|
2
|
+
import { Validator } from '../../../validator';
|
|
3
|
+
import { Overlay } from './overlay';
|
|
4
|
+
|
|
5
|
+
describe('Rules', () => {
|
|
6
|
+
|
|
7
|
+
describe('Overlay', () => {
|
|
8
|
+
|
|
9
|
+
it('should indicate that class exists', () => {
|
|
10
|
+
expect(Overlay).toBeDefined();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
let fakeDom;
|
|
14
|
+
|
|
15
|
+
new Overlay().registerValidator();
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
fakeDom = document.createElement('div');
|
|
19
|
+
fakeDom.id = 'fakedom';
|
|
20
|
+
document.body.appendChild(fakeDom);
|
|
21
|
+
|
|
22
|
+
Validator.reset();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
DomUtility.remove(document.getElementById('fakedom'));
|
|
27
|
+
fakeDom = undefined;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should return 1 report when there is one known Overlay detected', () => {
|
|
31
|
+
fakeDom.innerHTML = '<script src="https://cdn.userway.org/widget.js"></script>';
|
|
32
|
+
const nodes = DomUtility.querySelectorAllExclude('script', fakeDom) as HTMLScriptElement[];
|
|
33
|
+
|
|
34
|
+
new Overlay().validate(nodes);
|
|
35
|
+
|
|
36
|
+
expect(Object.keys(Validator.getReports()).length).toBe(1);
|
|
37
|
+
expect(Validator.getReport('report_0').message).toBe('Accessibility overlay UserWay has been detected on the page. Overlays are third-party widgets that attempt to automatically fix the accessibility issues of page they are added to. Therefore the results from the scanning may not be accurate.');
|
|
38
|
+
expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('script');
|
|
39
|
+
expect(Validator.getReport('report_0').ruleId).toBe('overlay');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should return 1 report when there is one known Overlay detected inside HTML', () => {
|
|
43
|
+
fakeDom.innerHTML = '<div data-src="https://cdn.userway.org/widget.js"></div>';
|
|
44
|
+
const nodes = DomUtility.querySelectorAllExclude('script', fakeDom) as HTMLScriptElement[];
|
|
45
|
+
|
|
46
|
+
new Overlay().validate(nodes);
|
|
47
|
+
|
|
48
|
+
expect(Object.keys(Validator.getReports()).length).toBe(1);
|
|
49
|
+
expect(Validator.getReport('report_0').message).toBe('Accessibility overlay UserWay has been detected on the page. Overlays are third-party widgets that attempt to automatically fix the accessibility issues of page they are added to. Therefore the results from the scanning may not be accurate.');
|
|
50
|
+
expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('html');
|
|
51
|
+
expect(Validator.getReport('report_0').ruleId).toBe('overlay');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should return reports for all known detected overlays', () => {
|
|
55
|
+
let overlays: string = '';
|
|
56
|
+
|
|
57
|
+
overlays += '<script src="https://cdn.acsbap.com/widget.js"></script>';
|
|
58
|
+
overlays += '<script src="https://cdn.acsbapp.com/widget.js"></script>';
|
|
59
|
+
overlays += '<script src="https://cdn.audioeye.com/widget.js"></script>';
|
|
60
|
+
overlays += '<script src="https://cdn.nagich.com/widget.js"></script>';
|
|
61
|
+
overlays += '<script src="https://cdn.nagich.co.il/widget.js"></script>';
|
|
62
|
+
overlays += '<script src="https://cdn.maxaccess.io/widget.js"></script>';
|
|
63
|
+
overlays += '<script src="https://cdn.truabilities.com/widget.js"></script>';
|
|
64
|
+
overlays += '<script src="https://cdn.user1st.info/widget.js"></script>';
|
|
65
|
+
overlays += '<script src="https://cdn.userway.org/widget.js"></script>';
|
|
66
|
+
|
|
67
|
+
fakeDom.innerHTML = overlays;
|
|
68
|
+
|
|
69
|
+
const nodes = DomUtility.querySelectorAllExclude('script', fakeDom) as HTMLScriptElement[];
|
|
70
|
+
|
|
71
|
+
new Overlay().validate(nodes);
|
|
72
|
+
|
|
73
|
+
expect(Object.keys(Validator.getReports()).length).toBe(9);
|
|
74
|
+
|
|
75
|
+
expect(Validator.getReport('report_0').message).toBe('Accessibility overlay AccessiBe has been detected on the page. Overlays are third-party widgets that attempt to automatically fix the accessibility issues of page they are added to. Therefore the results from the scanning may not be accurate.');
|
|
76
|
+
expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('script');
|
|
77
|
+
expect(Validator.getReport('report_0').ruleId).toBe('overlay');
|
|
78
|
+
|
|
79
|
+
expect(Validator.getReport('report_1').message).toBe('Accessibility overlay AccessiBe has been detected on the page. Overlays are third-party widgets that attempt to automatically fix the accessibility issues of page they are added to. Therefore the results from the scanning may not be accurate.');
|
|
80
|
+
expect(Validator.getReport('report_1').node.nodeName.toLowerCase()).toBe('script');
|
|
81
|
+
expect(Validator.getReport('report_1').ruleId).toBe('overlay');
|
|
82
|
+
|
|
83
|
+
expect(Validator.getReport('report_2').message).toBe('Accessibility overlay AudioEye has been detected on the page. Overlays are third-party widgets that attempt to automatically fix the accessibility issues of page they are added to. Therefore the results from the scanning may not be accurate.');
|
|
84
|
+
expect(Validator.getReport('report_2').node.nodeName.toLowerCase()).toBe('script');
|
|
85
|
+
expect(Validator.getReport('report_2').ruleId).toBe('overlay');
|
|
86
|
+
|
|
87
|
+
expect(Validator.getReport('report_3').message).toBe('Accessibility overlay EqualWeb has been detected on the page. Overlays are third-party widgets that attempt to automatically fix the accessibility issues of page they are added to. Therefore the results from the scanning may not be accurate.');
|
|
88
|
+
expect(Validator.getReport('report_3').node.nodeName.toLowerCase()).toBe('script');
|
|
89
|
+
expect(Validator.getReport('report_3').ruleId).toBe('overlay');
|
|
90
|
+
|
|
91
|
+
expect(Validator.getReport('report_4').message).toBe('Accessibility overlay EqualWeb has been detected on the page. Overlays are third-party widgets that attempt to automatically fix the accessibility issues of page they are added to. Therefore the results from the scanning may not be accurate.');
|
|
92
|
+
expect(Validator.getReport('report_4').node.nodeName.toLowerCase()).toBe('script');
|
|
93
|
+
expect(Validator.getReport('report_4').ruleId).toBe('overlay');
|
|
94
|
+
|
|
95
|
+
expect(Validator.getReport('report_5').message).toBe('Accessibility overlay MaxAccess has been detected on the page. Overlays are third-party widgets that attempt to automatically fix the accessibility issues of page they are added to. Therefore the results from the scanning may not be accurate.');
|
|
96
|
+
expect(Validator.getReport('report_5').node.nodeName.toLowerCase()).toBe('script');
|
|
97
|
+
expect(Validator.getReport('report_5').ruleId).toBe('overlay');
|
|
98
|
+
|
|
99
|
+
expect(Validator.getReport('report_6').message).toBe('Accessibility overlay TruAbilities has been detected on the page. Overlays are third-party widgets that attempt to automatically fix the accessibility issues of page they are added to. Therefore the results from the scanning may not be accurate.');
|
|
100
|
+
expect(Validator.getReport('report_6').node.nodeName.toLowerCase()).toBe('script');
|
|
101
|
+
expect(Validator.getReport('report_6').ruleId).toBe('overlay');
|
|
102
|
+
|
|
103
|
+
expect(Validator.getReport('report_7').message).toBe('Accessibility overlay User1st has been detected on the page. Overlays are third-party widgets that attempt to automatically fix the accessibility issues of page they are added to. Therefore the results from the scanning may not be accurate.');
|
|
104
|
+
expect(Validator.getReport('report_7').node.nodeName.toLowerCase()).toBe('script');
|
|
105
|
+
expect(Validator.getReport('report_7').ruleId).toBe('overlay');
|
|
106
|
+
|
|
107
|
+
expect(Validator.getReport('report_8').message).toBe('Accessibility overlay UserWay has been detected on the page. Overlays are third-party widgets that attempt to automatically fix the accessibility issues of page they are added to. Therefore the results from the scanning may not be accurate.');
|
|
108
|
+
expect(Validator.getReport('report_8').node.nodeName.toLowerCase()).toBe('script');
|
|
109
|
+
expect(Validator.getReport('report_8').ruleId).toBe('overlay');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should return no reports when there are no overlays on the page', () => {
|
|
113
|
+
fakeDom.innerHTML = '<script src="empty.js"></script>';
|
|
114
|
+
const nodes = DomUtility.querySelectorAllExclude('script', fakeDom) as HTMLScriptElement[];
|
|
115
|
+
|
|
116
|
+
new Overlay().validate(nodes);
|
|
117
|
+
|
|
118
|
+
expect(Object.keys(Validator.getReports()).length).toBe(0);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { CATEGORY_TYPE } from '../../../constants/categoryType';
|
|
2
|
+
import { IIssueReport } from '../../../interfaces/rule-issue.interface';
|
|
3
|
+
import { TextUtility } from '../../../utils/text';
|
|
4
|
+
import { TranslateService } from '../../../services/translate';
|
|
5
|
+
import { $accessibilityAuditRules, $severity } from '../../../constants/accessibility';
|
|
6
|
+
import { AbstractRule, IAbstractRuleConfig } from '../../abstract-rule';
|
|
7
|
+
import { Context } from '../../../interfaces/context.interface';
|
|
8
|
+
|
|
9
|
+
export class Overlay extends AbstractRule {
|
|
10
|
+
protected selector: string = 'script';
|
|
11
|
+
|
|
12
|
+
protected ruleConfig: IAbstractRuleConfig = {
|
|
13
|
+
id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.overlay),
|
|
14
|
+
links: [
|
|
15
|
+
{
|
|
16
|
+
content: 'The Many Pitfalls of Accessibility Overlays',
|
|
17
|
+
url: 'https://www.essentialaccessibility.com/blog/the-many-pitfalls-of-accessibility-overlays'
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
recommendations: [],
|
|
21
|
+
severity: $severity.high,
|
|
22
|
+
type: CATEGORY_TYPE.BEST_PRACTICE
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
public validate(scripts: HTMLScriptElement[]): void {
|
|
26
|
+
let foundedInScripts: boolean = false;
|
|
27
|
+
|
|
28
|
+
const findByScripts = (_overlayEntries: [string, string[]][]): void => {
|
|
29
|
+
const findOverlay = (script: HTMLScriptElement): void => {
|
|
30
|
+
|
|
31
|
+
let url: URL | null;
|
|
32
|
+
|
|
33
|
+
if (script.src.length === 0) {
|
|
34
|
+
// Note: this means script is inline embedded
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
url = new URL(script.src);
|
|
40
|
+
} catch (_) {
|
|
41
|
+
url = null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (url === null) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const hostname: string = url.hostname;
|
|
49
|
+
const foundedOverlays: Map<string, null> = new Map();
|
|
50
|
+
|
|
51
|
+
for (const [overlayName, overlayUrls] of _overlayEntries) {
|
|
52
|
+
|
|
53
|
+
for (const overlayUrl of overlayUrls) {
|
|
54
|
+
if (hostname.includes(overlayUrl)) {
|
|
55
|
+
foundedOverlays.set(overlayName, null);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (Array.from(foundedOverlays).length === 0) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const reportMessage: string = TranslateService.instant('overlay_report_message', Array.from(foundedOverlays.keys()).join(', '));
|
|
65
|
+
|
|
66
|
+
const report: IIssueReport = {
|
|
67
|
+
message: reportMessage,
|
|
68
|
+
node: script,
|
|
69
|
+
ruleId: this.ruleConfig.id
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
this.validator.report(report);
|
|
73
|
+
|
|
74
|
+
foundedInScripts = true;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
scripts.forEach(findOverlay);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const findThroughHtml = (_overlayEntries: [string, string[]][]): void => {
|
|
81
|
+
const context: Context = this.context;
|
|
82
|
+
let html: string = '';
|
|
83
|
+
|
|
84
|
+
if (typeof (context as any).innerHTML === 'string') {
|
|
85
|
+
html = (context as any).innerHTML;
|
|
86
|
+
} else if ((context as any).outerHTML === 'string') {
|
|
87
|
+
html = (context as any).outerHTML;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (html.trim().length === 0) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const foundedOverlays: Map<string, null> = new Map();
|
|
95
|
+
|
|
96
|
+
for (const [overlayName, overlayUrls] of _overlayEntries) {
|
|
97
|
+
|
|
98
|
+
for (const overlayUrl of overlayUrls) {
|
|
99
|
+
if (html.includes(overlayUrl)) {
|
|
100
|
+
foundedOverlays.set(overlayName, null);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (Array.from(foundedOverlays).length === 0) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const reportMessage: string = TranslateService.instant('overlay_report_message', Array.from(foundedOverlays.keys()).join(', '));
|
|
110
|
+
|
|
111
|
+
const report: IIssueReport = {
|
|
112
|
+
message: reportMessage,
|
|
113
|
+
node: context,
|
|
114
|
+
ruleId: this.ruleConfig.id
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
this.validator.report(report);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Note: key of below object is later used as a name of overlay vendor
|
|
121
|
+
const overlay: { [key: string]: string[] } = {
|
|
122
|
+
AccessiBe: ['acsbap.com', 'acsbapp.com'],
|
|
123
|
+
AudioEye: ['audioeye.com'],
|
|
124
|
+
EqualWeb: ['nagich.com', 'nagich.co.il'],
|
|
125
|
+
MaxAccess: ['maxaccess.io'],
|
|
126
|
+
TruAbilities: ['truabilities.com'],
|
|
127
|
+
User1st: ['user1st.info'],
|
|
128
|
+
UserWay: ['userway.org']
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const overlayEntries: [string, string[]][] = Object.entries(overlay);
|
|
132
|
+
|
|
133
|
+
if (scripts.length > 0) {
|
|
134
|
+
findByScripts(overlayEntries);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (foundedInScripts === false) {
|
|
138
|
+
findThroughHtml(overlayEntries);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { RoleApplication } from './role-application';
|
|
2
|
+
import { Validator } from '../../../validator';
|
|
3
|
+
import { DomUtility } from '../../../utils/dom';
|
|
4
|
+
|
|
5
|
+
describe('Rules', () => {
|
|
6
|
+
|
|
7
|
+
describe('RoleApplication', () => {
|
|
8
|
+
|
|
9
|
+
let fakeDom;
|
|
10
|
+
|
|
11
|
+
new RoleApplication().registerValidator();
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
fakeDom = document.createElement('div');
|
|
15
|
+
fakeDom.id = 'fakedom';
|
|
16
|
+
document.body.appendChild(fakeDom);
|
|
17
|
+
|
|
18
|
+
Validator.reset();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
DomUtility.remove(document.getElementById('fakedom'));
|
|
23
|
+
fakeDom = undefined;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should return correct data for p with role="application"', () => {
|
|
27
|
+
fakeDom.innerHTML = '<p role="application"></p>';
|
|
28
|
+
const nodes = DomUtility.querySelectorAllExclude('[role="application"]', fakeDom) as HTMLElement[];
|
|
29
|
+
|
|
30
|
+
new RoleApplication().validate(nodes);
|
|
31
|
+
|
|
32
|
+
expect(Object.keys(Validator.getReports()).length).toBe(1);
|
|
33
|
+
expect(Validator.getReport('report_0').message).toBe('Use <code>role="application"</code> carefully as it is used to denote a region of a web application that is to be treated like a desktop application, not like a regular web page.');
|
|
34
|
+
expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('p');
|
|
35
|
+
expect(Validator.getReport('report_0').ruleId).toBe('role-application');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should return no reports in case no elements with p with role="application"', () => {
|
|
39
|
+
fakeDom.innerHTML = '<p>Test</p>';
|
|
40
|
+
const nodes = DomUtility.querySelectorAllExclude('[role="application"]', fakeDom) as HTMLElement[];
|
|
41
|
+
|
|
42
|
+
new RoleApplication().validate(nodes);
|
|
43
|
+
|
|
44
|
+
expect(Object.keys(Validator.getReports()).length).toBe(0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { CATEGORY_TYPE } from '../../../constants/categoryType';
|
|
2
|
+
import { IIssueReport } from '../../../interfaces/rule-issue.interface';
|
|
3
|
+
import { TextUtility } from '../../../utils/text';
|
|
4
|
+
import { TranslateService } from '../../../services/translate';
|
|
5
|
+
import { $severity } from '../../../constants/accessibility';
|
|
6
|
+
import { $accessibilityAuditRules } from '../../../constants/accessibility';
|
|
7
|
+
import { AbstractRule, IAbstractRuleConfig } from '../../abstract-rule';
|
|
8
|
+
|
|
9
|
+
export class RoleApplication extends AbstractRule {
|
|
10
|
+
protected selector: string = '[role="application"]';
|
|
11
|
+
|
|
12
|
+
protected ruleConfig: IAbstractRuleConfig = {
|
|
13
|
+
id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.role_application),
|
|
14
|
+
links: [
|
|
15
|
+
{
|
|
16
|
+
content: 'Using ARIA role=application',
|
|
17
|
+
url: 'https://w3c.github.io/aria-in-html/'
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
recommendations: [],
|
|
21
|
+
severity: $severity.critical,
|
|
22
|
+
type: CATEGORY_TYPE.BEST_PRACTICE
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
public validate(elements: HTMLElement[]): void {
|
|
26
|
+
const checkTabindex = (element: HTMLElement): void => {
|
|
27
|
+
const report: IIssueReport = {
|
|
28
|
+
message: TranslateService.instant('role_application_report_message'),
|
|
29
|
+
node: element,
|
|
30
|
+
ruleId: this.ruleConfig.id
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
this.validator.report(report);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
elements.forEach(checkTabindex);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Css } from '../../../utils/css';
|
|
2
|
+
import { CATEGORY_TYPE } from '../../../constants/categoryType';
|
|
3
|
+
import { IIssueReport } from '../../../interfaces/rule-issue.interface';
|
|
4
|
+
import { TextUtility } from '../../../utils/text';
|
|
5
|
+
import { TranslateService } from '../../../services/translate';
|
|
6
|
+
import { $severity } from '../../../constants/accessibility';
|
|
7
|
+
import { $accessibilityAuditRules } from '../../../constants/accessibility';
|
|
8
|
+
import { AbstractRule, IAbstractRuleConfig } from '../../abstract-rule';
|
|
9
|
+
|
|
10
|
+
export class RtlContent extends AbstractRule {
|
|
11
|
+
protected selector: string = `*${[
|
|
12
|
+
':root',
|
|
13
|
+
'head',
|
|
14
|
+
'style',
|
|
15
|
+
'script',
|
|
16
|
+
'meta',
|
|
17
|
+
'link',
|
|
18
|
+
'br',
|
|
19
|
+
'hr',
|
|
20
|
+
'object',
|
|
21
|
+
'path',
|
|
22
|
+
'g',
|
|
23
|
+
'filter',
|
|
24
|
+
'img',
|
|
25
|
+
'input',
|
|
26
|
+
'iframe',
|
|
27
|
+
'code',
|
|
28
|
+
':empty'
|
|
29
|
+
].map((i: string): string => {
|
|
30
|
+
return `:not(${i})`;
|
|
31
|
+
}).join('')}`;
|
|
32
|
+
|
|
33
|
+
protected ruleConfig: IAbstractRuleConfig = {
|
|
34
|
+
id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.rtl_content),
|
|
35
|
+
links: [
|
|
36
|
+
{
|
|
37
|
+
content: 'Right to left implementation tricks',
|
|
38
|
+
url: 'http://www.ctomczyk.pl/right-to-left-implementation-tricks/642/'
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
recommendations: [],
|
|
42
|
+
severity: $severity.info,
|
|
43
|
+
type: CATEGORY_TYPE.BEST_PRACTICE
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
public validate(elements: HTMLElement[]): any {
|
|
47
|
+
const nodesToReport: HTMLElement[] = [];
|
|
48
|
+
|
|
49
|
+
const reportRTL = (element: HTMLElement): void => {
|
|
50
|
+
if (element.nodeType !== element.ELEMENT_NODE) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const direction: string | null = Css.getStyle(element, 'direction');
|
|
55
|
+
|
|
56
|
+
if (typeof direction === 'string' && direction.toLowerCase() === 'rtl') {
|
|
57
|
+
nodesToReport.push(element);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
elements.forEach(reportRTL);
|
|
62
|
+
|
|
63
|
+
const createReport = (element: HTMLElement): void => {
|
|
64
|
+
const problem: IIssueReport = {
|
|
65
|
+
message: TranslateService.instant('rtl_content_report_message'),
|
|
66
|
+
node: element,
|
|
67
|
+
ruleId: this.ruleConfig.id
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
this.validator.report(problem);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
nodesToReport.forEach(createReport);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { CATEGORY_TYPE } from '../../../constants/categoryType';
|
|
2
|
+
import { IIssueReport } from '../../../interfaces/rule-issue.interface';
|
|
3
|
+
import { TextUtility } from '../../../utils/text';
|
|
4
|
+
import { TranslateService } from '../../../services/translate';
|
|
5
|
+
import { $severity } from '../../../constants/accessibility';
|
|
6
|
+
import { $accessibilityAuditRules } from '../../../constants/accessibility';
|
|
7
|
+
import { AbstractRule, IAbstractRuleConfig } from '../../abstract-rule';
|
|
8
|
+
|
|
9
|
+
export class UnclearAnchorUri extends AbstractRule {
|
|
10
|
+
protected selector: string = 'a[href="#"], a[href*=javascript\\:], a[href=""]';
|
|
11
|
+
|
|
12
|
+
protected ruleConfig: IAbstractRuleConfig = {
|
|
13
|
+
id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.unclear_anchor_uri),
|
|
14
|
+
links: [
|
|
15
|
+
{
|
|
16
|
+
content: 'Making AJAX applications crawlable',
|
|
17
|
+
url: 'https://developers.google.com/webmasters/ajax-crawling/docs/learn-more#what-the-user-sees-what-the-crawler-sees'
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
recommendations: [],
|
|
21
|
+
severity: $severity.high,
|
|
22
|
+
type: CATEGORY_TYPE.BEST_PRACTICE
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
public validate(anchorElements: HTMLAnchorElement[]): void {
|
|
26
|
+
const checkHref = (anchorElement: HTMLAnchorElement): void => {
|
|
27
|
+
const hrefAttr: string | null = anchorElement.getAttribute('href');
|
|
28
|
+
|
|
29
|
+
if (hrefAttr === null) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const report: IIssueReport = {
|
|
34
|
+
message: TranslateService.instant('unclear_uri_on_a_report_message_1', [anchorElement.href]),
|
|
35
|
+
node: anchorElement,
|
|
36
|
+
ruleId: this.ruleConfig.id
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
this.validator.report(report);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const nodesLength: number = anchorElements.length;
|
|
43
|
+
|
|
44
|
+
if (nodesLength) {
|
|
45
|
+
anchorElements.forEach(checkHref);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
package/aslint/app/rules/aslint/unsupported-role-on-element/unsupported-role-on-element.test.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { UnsupportedRoleOnElement } from './unsupported-role-on-element';
|
|
2
|
+
|
|
3
|
+
describe('Rules', () => {
|
|
4
|
+
|
|
5
|
+
describe('UnsupportedRoleOnElement', () => {
|
|
6
|
+
|
|
7
|
+
it('should indicate that class exists', () => {
|
|
8
|
+
expect(UnsupportedRoleOnElement).toBeDefined();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { CATEGORY_TYPE } from '../../../constants/categoryType';
|
|
2
|
+
import { TAG_TO_IMPLICIT_SEMANTIC_INFO } from '../../../constants/aria';
|
|
3
|
+
import { IIssueReport } from '../../../interfaces/rule-issue.interface';
|
|
4
|
+
import { TextUtility } from '../../../utils/text';
|
|
5
|
+
import { TranslateService } from '../../../services/translate';
|
|
6
|
+
import { $severity } from '../../../constants/accessibility';
|
|
7
|
+
import { $accessibilityAuditRules } from '../../../constants/accessibility';
|
|
8
|
+
import { AbstractRule, IAbstractRuleConfig } from '../../abstract-rule';
|
|
9
|
+
|
|
10
|
+
export class UnsupportedRoleOnElement extends AbstractRule {
|
|
11
|
+
protected selector: string = 'a[href="#"], a[href*=javascript\\:], a[href=""]';
|
|
12
|
+
|
|
13
|
+
protected ruleConfig: IAbstractRuleConfig = {
|
|
14
|
+
id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.unsupported_role_on_element),
|
|
15
|
+
links: [],
|
|
16
|
+
recommendations: [],
|
|
17
|
+
severity: $severity.high,
|
|
18
|
+
type: CATEGORY_TYPE.BEST_PRACTICE
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
public validate(anchorElements: HTMLAnchorElement[]): void {
|
|
22
|
+
const checkSingleNode = (anchorElement: HTMLAnchorElement): void => {
|
|
23
|
+
const tagName: string = anchorElement.nodeName.toUpperCase();
|
|
24
|
+
const roleValue: string | null = anchorElement.getAttribute('role');
|
|
25
|
+
|
|
26
|
+
if (typeof TAG_TO_IMPLICIT_SEMANTIC_INFO[tagName] === 'undefined') {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (roleValue === null) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (
|
|
35
|
+
Array.isArray(TAG_TO_IMPLICIT_SEMANTIC_INFO[tagName][0].allowed) &&
|
|
36
|
+
(TAG_TO_IMPLICIT_SEMANTIC_INFO[tagName][0].allowed as string[]).indexOf(
|
|
37
|
+
roleValue
|
|
38
|
+
) !== -1
|
|
39
|
+
) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const reportMessage: string = TranslateService.instant(
|
|
44
|
+
'unsupported_role_on_element_report_message',
|
|
45
|
+
[roleValue]
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const report: IIssueReport = {
|
|
49
|
+
message: reportMessage,
|
|
50
|
+
node: anchorElement,
|
|
51
|
+
ruleId: this.ruleConfig.id
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
this.validator.report(report);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const nodesLength: number = anchorElements.length;
|
|
58
|
+
|
|
59
|
+
if (nodesLength > 0) {
|
|
60
|
+
anchorElements.forEach(checkSingleNode);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|