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,48 @@
|
|
|
1
|
+
import { LabelDuplicatedContentTitle } from './label-duplicated-content-title';
|
|
2
|
+
import { Validator } from '../../../validator';
|
|
3
|
+
import { DomUtility } from '../../../utils/dom';
|
|
4
|
+
|
|
5
|
+
describe('Rules', () => {
|
|
6
|
+
|
|
7
|
+
describe('LabelDuplicatedContentTitle', () => {
|
|
8
|
+
|
|
9
|
+
let fakeDom;
|
|
10
|
+
|
|
11
|
+
new LabelDuplicatedContentTitle().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 a element without href attribute', () => {
|
|
27
|
+
fakeDom.innerHTML = '<label title="main">main</label>';
|
|
28
|
+
const nodes = DomUtility.querySelectorAllExclude('[title=main]', fakeDom) as HTMLElement[];
|
|
29
|
+
|
|
30
|
+
new LabelDuplicatedContentTitle().validate(nodes);
|
|
31
|
+
|
|
32
|
+
expect(Object.keys(Validator.getReports()).length).toBe(1);
|
|
33
|
+
expect(Validator.getReport('report_0').message).toBe('This element has the same content as its <code>title</code> attribute. Consider removing <code>title</code> as some screen readers read both.');
|
|
34
|
+
expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('label');
|
|
35
|
+
expect(Validator.getReport('report_0').ruleId).toBe('label-duplicated-content-title');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should return no reports in case of a element with href attribute', () => {
|
|
39
|
+
fakeDom.innerHTML = '<label title="main">main1</label>';
|
|
40
|
+
const nodes = DomUtility.querySelectorAllExclude('[title=main]', fakeDom) as HTMLElement[];
|
|
41
|
+
|
|
42
|
+
new LabelDuplicatedContentTitle().validate(nodes);
|
|
43
|
+
|
|
44
|
+
expect(Object.keys(Validator.getReports()).length).toBe(0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
});
|
|
48
|
+
});
|
package/aslint/app/rules/aslint/label-duplicated-content-title/label-duplicated-content-title.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
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 { $accessibilityAuditRules, $severity } from '../../../constants/accessibility';
|
|
7
|
+
import { AbstractRule, IAbstractRuleConfig } from '../../abstract-rule';
|
|
8
|
+
|
|
9
|
+
export class LabelDuplicatedContentTitle extends AbstractRule {
|
|
10
|
+
protected selector: string = 'label[title]';
|
|
11
|
+
protected ruleConfig: IAbstractRuleConfig = {
|
|
12
|
+
id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.label_duplicated_content_title),
|
|
13
|
+
links: [],
|
|
14
|
+
recommendations: [],
|
|
15
|
+
severity: $severity.high,
|
|
16
|
+
type: CATEGORY_TYPE.BEST_PRACTICE
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
public validate(elements: HTMLElement[]): void {
|
|
20
|
+
const checkLabel = (element: HTMLElement): void => {
|
|
21
|
+
const labelContent: string = DomUtility.getText(element, true);
|
|
22
|
+
const labelTitle: string = element.title;
|
|
23
|
+
|
|
24
|
+
if (labelContent === labelTitle) {
|
|
25
|
+
const report: IIssueReport = {
|
|
26
|
+
message: TranslateService.instant('label_duplicated_content_title_report_message'),
|
|
27
|
+
node: element,
|
|
28
|
+
ruleId: this.ruleConfig.id
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
this.validator.report(report);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
elements.forEach(checkLabel);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { DomUtility } from '../../../utils/dom';
|
|
2
|
+
import { Validator } from '../../../validator';
|
|
3
|
+
import { LinksLanguageDestination } from './links-language-destination';
|
|
4
|
+
|
|
5
|
+
describe('Rules', () => {
|
|
6
|
+
|
|
7
|
+
describe('LinksLanguageDestination', () => {
|
|
8
|
+
|
|
9
|
+
it('should indicate that class exists', () => {
|
|
10
|
+
expect(LinksLanguageDestination).toBeDefined();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
let fakeDom;
|
|
14
|
+
|
|
15
|
+
new LinksLanguageDestination().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 no reports when there is an anchor element with url pointed within page base uri', () => {
|
|
31
|
+
fakeDom.innerHTML = '<a href="http://localhost/example">example</a>';
|
|
32
|
+
|
|
33
|
+
new LinksLanguageDestination().validate(Array.from(document.links));
|
|
34
|
+
|
|
35
|
+
expect(Object.keys(Validator.getReports()).length).toBe(0);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should return 1 report when there is an anchor element with url pointed outside of base uri', () => {
|
|
39
|
+
fakeDom.innerHTML = '<a href="http://example.com/example">example</a>';
|
|
40
|
+
|
|
41
|
+
new LinksLanguageDestination().validate(Array.from(document.links));
|
|
42
|
+
|
|
43
|
+
expect(Object.keys(Validator.getReports()).length).toBe(1);
|
|
44
|
+
expect(Validator.getReport('report_0').message).toBe('Following url <code>http://example.com/example</code> points to an external resource. If the content behind the link is in a different language then consider add some text or graphic to the link indicating that the target document is in another language.');
|
|
45
|
+
expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('a');
|
|
46
|
+
expect(Validator.getReport('report_0').ruleId).toBe('links-language-destination');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
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 LinksLanguageDestination extends AbstractRule {
|
|
10
|
+
protected selector: () => (HTMLAnchorElement | HTMLAreaElement)[] = (): (HTMLAnchorElement | HTMLAreaElement)[] => {
|
|
11
|
+
return Array.from(document.links);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
protected ruleConfig: IAbstractRuleConfig = {
|
|
15
|
+
id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.links_language_destination),
|
|
16
|
+
links: [
|
|
17
|
+
{
|
|
18
|
+
content: 'Indicating the language of a link destination',
|
|
19
|
+
url: 'https://www.w3.org/International/questions/qa-link-lang'
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
recommendations: [],
|
|
23
|
+
severity: $severity.info,
|
|
24
|
+
type: CATEGORY_TYPE.BEST_PRACTICE
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
public validate(links: (HTMLAnchorElement | HTMLAreaElement)[]): void {
|
|
28
|
+
let baseURI: string;
|
|
29
|
+
|
|
30
|
+
if (typeof document.baseURI === 'string') {
|
|
31
|
+
baseURI = document.baseURI;
|
|
32
|
+
} else {
|
|
33
|
+
baseURI = document.URL || document.location.href;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const onlyIncludeExternalLinks = (link: HTMLAnchorElement | HTMLAreaElement): boolean => {
|
|
37
|
+
if (link.hostname.length > 0 && link.href.length > 0) {
|
|
38
|
+
let baseHostname: string;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const url: URL = new URL(link.href, baseURI);
|
|
42
|
+
|
|
43
|
+
baseHostname = url.hostname;
|
|
44
|
+
} catch (_) {
|
|
45
|
+
baseHostname = document.location.hostname;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return baseHostname !== window.location.hostname;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return false;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const externalLinks: (HTMLAnchorElement | HTMLAreaElement)[] = links.filter(onlyIncludeExternalLinks);
|
|
55
|
+
|
|
56
|
+
const reportIssue = (anchorElement: (HTMLAnchorElement | HTMLAreaElement)): void => {
|
|
57
|
+
const reportMessage: string = TranslateService.instant('links_language_destination_report_message', [TextUtility.escape(anchorElement.href)]);
|
|
58
|
+
|
|
59
|
+
const report: IIssueReport = {
|
|
60
|
+
message: reportMessage,
|
|
61
|
+
node: anchorElement,
|
|
62
|
+
ruleId: this.ruleConfig.id
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
this.validator.report(report);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
externalLinks.forEach(reportIssue);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { MainElementOnlyOne } from './main-element-only-one';
|
|
2
|
+
import { Validator } from '../../../validator';
|
|
3
|
+
import { DomUtility } from '../../../utils/dom';
|
|
4
|
+
|
|
5
|
+
describe('Rules', () => {
|
|
6
|
+
|
|
7
|
+
describe('MainElementOnlyOne', () => {
|
|
8
|
+
|
|
9
|
+
it('should indicate that class exists', () => {
|
|
10
|
+
expect(MainElementOnlyOne).toBeDefined();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
let fakeDom;
|
|
14
|
+
|
|
15
|
+
new MainElementOnlyOne().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 one report when there is more than 1 main element', () => {
|
|
31
|
+
fakeDom.innerHTML = '<main>Text1</main><main>Text2</main>';
|
|
32
|
+
const nodes = DomUtility.querySelectorAllExclude('main', fakeDom) as HTMLElement[];
|
|
33
|
+
|
|
34
|
+
new MainElementOnlyOne().validate(nodes);
|
|
35
|
+
|
|
36
|
+
expect(Object.keys(Validator.getReports()).length).toBe(2);
|
|
37
|
+
expect(Validator.getReport('report_0').message).toBe('You have defined multiple (2) <code><main></code> elements. Assistive technology users expect one main content block and may miss subsequent <code><main></code> blocks.');
|
|
38
|
+
expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('main');
|
|
39
|
+
expect(Validator.getReport('report_0').ruleId).toBe('main-element-only-one');
|
|
40
|
+
expect(Validator.getReport('report_1').message).toBe('You have defined multiple (2) <code><main></code> elements. Assistive technology users expect one main content block and may miss subsequent <code><main></code> blocks.');
|
|
41
|
+
expect(Validator.getReport('report_1').node.nodeName.toLowerCase()).toBe('main');
|
|
42
|
+
expect(Validator.getReport('report_1').ruleId).toBe('main-element-only-one');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should return no reports when there is 1 main element', () => {
|
|
46
|
+
fakeDom.innerHTML = '<main>Text1</main>';
|
|
47
|
+
const nodes = DomUtility.querySelectorAllExclude('main', fakeDom) as HTMLElement[];
|
|
48
|
+
|
|
49
|
+
new MainElementOnlyOne().validate(nodes);
|
|
50
|
+
|
|
51
|
+
expect(Object.keys(Validator.getReports()).length).toBe(0);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
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 { $auditRuleNodeSkipReason, $severity } from '../../../constants/accessibility';
|
|
6
|
+
import { $accessibilityAuditRules } from '../../../constants/accessibility';
|
|
7
|
+
import { AbstractRule, IAbstractRuleConfig } from '../../abstract-rule';
|
|
8
|
+
import { DomUtility } from '../../../utils/dom';
|
|
9
|
+
import { Config } from '../../../config';
|
|
10
|
+
import { $runnerSettings } from '../../../constants/aslint';
|
|
11
|
+
import { Css } from '../../../utils/css';
|
|
12
|
+
|
|
13
|
+
export class MainElementOnlyOne extends AbstractRule {
|
|
14
|
+
private appConfig: Config = Config.getInstance();
|
|
15
|
+
|
|
16
|
+
protected selector: string = 'main';
|
|
17
|
+
|
|
18
|
+
protected ruleConfig: IAbstractRuleConfig = {
|
|
19
|
+
id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.main_element_only_one),
|
|
20
|
+
links: [
|
|
21
|
+
{
|
|
22
|
+
content: 'HTML/Elements/main',
|
|
23
|
+
url: 'https://www.w3.org/wiki/HTML/Elements/main#Point'
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
recommendations: [],
|
|
27
|
+
severity: $severity.high,
|
|
28
|
+
type: CATEGORY_TYPE.BEST_PRACTICE
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
private elementShouldBeSkipped(element: HTMLElement): boolean {
|
|
32
|
+
const styles: CSSStyleDeclaration | null = Css.getComputedStyle(element);
|
|
33
|
+
const ariaHidden: Attr | null = DomUtility.getElementAttribute(element, 'aria-hidden');
|
|
34
|
+
let elementShouldBeSkipped: boolean = true;
|
|
35
|
+
|
|
36
|
+
if (ariaHidden && ariaHidden.value === 'true') {
|
|
37
|
+
return elementShouldBeSkipped;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (this.appConfig.get($runnerSettings.includeHidden)) {
|
|
41
|
+
elementShouldBeSkipped = false;
|
|
42
|
+
|
|
43
|
+
return elementShouldBeSkipped;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (DomUtility.hasElementSemiOpacity(element, styles)) {
|
|
47
|
+
return elementShouldBeSkipped;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public validate(elements: HTMLElement[]): void {
|
|
54
|
+
if (elements.length < 2) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const checkForIssue = (element: HTMLElement): void => {
|
|
59
|
+
if (this.elementShouldBeSkipped(element)) {
|
|
60
|
+
this.validator.report({
|
|
61
|
+
message: '',
|
|
62
|
+
node: element,
|
|
63
|
+
ruleId: this.ruleConfig.id,
|
|
64
|
+
skipReason: $auditRuleNodeSkipReason.excludedFromScanning
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const reportMessage: string = TranslateService.instant('main_element_only_one_report_message', [elements.length, TextUtility.escape('main')]);
|
|
71
|
+
|
|
72
|
+
const report: IIssueReport = {
|
|
73
|
+
message: reportMessage,
|
|
74
|
+
node: element,
|
|
75
|
+
ruleId: this.ruleConfig.id
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
this.validator.report(report);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
elements.forEach(checkForIssue);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { MainLandmarkMustBeTopLevel } from './main-landmark-must-be-top-level';
|
|
2
|
+
|
|
3
|
+
describe('Rules', () => {
|
|
4
|
+
|
|
5
|
+
describe('MainLandmarkMustBeTopLevel', () => {
|
|
6
|
+
|
|
7
|
+
it('should indicate that class exists', () => {
|
|
8
|
+
expect(MainLandmarkMustBeTopLevel).toBeDefined();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
});
|
|
12
|
+
});
|
package/aslint/app/rules/aslint/main-landmark-must-be-top-level/main-landmark-must-be-top-level.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
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 { $accessibilityAuditRules } from '../../../constants/accessibility';
|
|
8
|
+
import { AbstractRule, IAbstractRuleConfig } from '../../abstract-rule';
|
|
9
|
+
|
|
10
|
+
export class MainLandmarkMustBeTopLevel extends AbstractRule {
|
|
11
|
+
protected selector: string = '[role="main"]';
|
|
12
|
+
protected ruleConfig: IAbstractRuleConfig = {
|
|
13
|
+
id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.main_landmark_must_be_top_level),
|
|
14
|
+
links: [
|
|
15
|
+
{
|
|
16
|
+
content: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: main role',
|
|
17
|
+
url: 'http://www.w3.org/TR/wai-aria/roles#main'
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
content: 'ARIA11: Using ARIA landmarks to identify regions of a page',
|
|
21
|
+
url: 'http://www.w3.org/TR/WCAG20-TECHS/ARIA11'
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
content: 'HTML5: The MAIN element',
|
|
25
|
+
url: 'http://www.w3.org/TR/html5/sections.html#the-main-element'
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
recommendations: [],
|
|
29
|
+
severity: $severity.high,
|
|
30
|
+
type: CATEGORY_TYPE.BEST_PRACTICE
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
public validate(elements: Element[]): void {
|
|
34
|
+
let parent: HTMLElement | null;
|
|
35
|
+
let parentNodeWithRole: Element | null = null;
|
|
36
|
+
let role: string | null;
|
|
37
|
+
const PARENT_ROLE_EXCEPTION: string[] = [
|
|
38
|
+
'application',
|
|
39
|
+
'document'
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
if (elements.length === 0 || elements[0].parentElement === null) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
parent = elements[0].parentElement;
|
|
47
|
+
|
|
48
|
+
while (parent && parent.getAttribute) {
|
|
49
|
+
role = parent.getAttribute('role');
|
|
50
|
+
|
|
51
|
+
if (typeof role === 'string' && PARENT_ROLE_EXCEPTION.indexOf(role) === -1) {
|
|
52
|
+
parentNodeWithRole = parent;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
parent = parent.parentElement;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (parentNodeWithRole === null) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const reportMessage: string = TranslateService.instant('main_landmark_must_be_top_level_report_message', [DomUtility.getEscapedOuterHTML(parentNodeWithRole)]);
|
|
64
|
+
|
|
65
|
+
const report: IIssueReport = {
|
|
66
|
+
message: reportMessage,
|
|
67
|
+
node: parentNodeWithRole,
|
|
68
|
+
ruleId: this.ruleConfig.id
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
this.validator.report(report);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { DomUtility } from '../../../utils/dom';
|
|
2
|
+
import { Css } from '../../../utils/css';
|
|
3
|
+
import { CATEGORY_TYPE } from '../../../constants/categoryType';
|
|
4
|
+
import { IIssueReport } from '../../../interfaces/rule-issue.interface';
|
|
5
|
+
import { TextUtility } from '../../../utils/text';
|
|
6
|
+
import { TranslateService } from '../../../services/translate';
|
|
7
|
+
import { $severity } from '../../../constants/accessibility';
|
|
8
|
+
import { $accessibilityAuditRules } from '../../../constants/accessibility';
|
|
9
|
+
import { AbstractRule, IAbstractRuleConfig } from '../../abstract-rule';
|
|
10
|
+
|
|
11
|
+
export class MinimumFontSize extends AbstractRule {
|
|
12
|
+
protected selector: string = `*${[
|
|
13
|
+
':root',
|
|
14
|
+
'head',
|
|
15
|
+
'title',
|
|
16
|
+
'style',
|
|
17
|
+
'script',
|
|
18
|
+
'noscript',
|
|
19
|
+
'meta',
|
|
20
|
+
'link',
|
|
21
|
+
'br',
|
|
22
|
+
'hr',
|
|
23
|
+
'object',
|
|
24
|
+
'path',
|
|
25
|
+
'g',
|
|
26
|
+
'desc',
|
|
27
|
+
'filter',
|
|
28
|
+
'img',
|
|
29
|
+
'input',
|
|
30
|
+
'iframe',
|
|
31
|
+
'code',
|
|
32
|
+
'defs',
|
|
33
|
+
':empty'
|
|
34
|
+
].map((i: string): string => {
|
|
35
|
+
return `:not(${i})`;
|
|
36
|
+
}).join('')}`;
|
|
37
|
+
|
|
38
|
+
protected ruleConfig: IAbstractRuleConfig = {
|
|
39
|
+
id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.minimum_font_size),
|
|
40
|
+
links: [],
|
|
41
|
+
recommendations: [],
|
|
42
|
+
severity: $severity.info,
|
|
43
|
+
type: CATEGORY_TYPE.BEST_PRACTICE
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
public validate(elements: HTMLElement[]): void {
|
|
47
|
+
const checkFontSize = (element: HTMLElement): void => {
|
|
48
|
+
if (DomUtility.hasDirectTextDescendant(element) === false) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const fontSizeStyle: string | null = Css.getStyle(element, 'font-size');
|
|
53
|
+
|
|
54
|
+
if (fontSizeStyle === null) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const fontSize: number = parseInt(fontSizeStyle, 10);
|
|
59
|
+
|
|
60
|
+
if (fontSize < 10) {
|
|
61
|
+
let report: IIssueReport;
|
|
62
|
+
|
|
63
|
+
if (DomUtility.isElementVisible(element) === false) {
|
|
64
|
+
const reportMessage: string = TranslateService.instant('minimum_font_size_report_message_1', [fontSize]);
|
|
65
|
+
|
|
66
|
+
report = {
|
|
67
|
+
message: reportMessage,
|
|
68
|
+
node: element,
|
|
69
|
+
ruleId: this.ruleConfig.id
|
|
70
|
+
};
|
|
71
|
+
} else {
|
|
72
|
+
const reportMessage: string = TranslateService.instant('minimum_font_size_report_message_2', [fontSize]);
|
|
73
|
+
|
|
74
|
+
report = {
|
|
75
|
+
message: reportMessage,
|
|
76
|
+
node: element,
|
|
77
|
+
ruleId: this.ruleConfig.id
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.validator.report(report);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
elements.forEach(checkFontSize);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { MissingHrefOnA } from './missing-href-on-a';
|
|
2
|
+
import { Validator } from '../../../validator';
|
|
3
|
+
import { DomUtility } from '../../../utils/dom';
|
|
4
|
+
|
|
5
|
+
describe('Rules', () => {
|
|
6
|
+
|
|
7
|
+
describe('MissingHrefOnA', () => {
|
|
8
|
+
|
|
9
|
+
let fakeDom;
|
|
10
|
+
|
|
11
|
+
new MissingHrefOnA().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 a element without href attribute', () => {
|
|
27
|
+
fakeDom.innerHTML = '<a>example.com</a>';
|
|
28
|
+
const nodes = DomUtility.querySelectorAllExclude('a:not([href])', fakeDom) as HTMLAnchorElement[];
|
|
29
|
+
|
|
30
|
+
new MissingHrefOnA().validate(nodes);
|
|
31
|
+
|
|
32
|
+
expect(Object.keys(Validator.getReports()).length).toBe(1);
|
|
33
|
+
expect(Validator.getReport('report_0').message).toBe('Missing attribute <code>href</code> on link. The user cannot navigate to this element using the keyboard. A better option here is to use a <code><a></code> element instead.');
|
|
34
|
+
expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('a');
|
|
35
|
+
expect(Validator.getReport('report_0').ruleId).toBe('missing-href-on-a');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should return no reports in case of a element with href attribute', () => {
|
|
39
|
+
fakeDom.innerHTML = '<a href="http://example.com">example.com</a>';
|
|
40
|
+
const nodes = DomUtility.querySelectorAllExclude('a:not([href])', fakeDom) as HTMLAnchorElement[];
|
|
41
|
+
|
|
42
|
+
new MissingHrefOnA().validate(nodes);
|
|
43
|
+
|
|
44
|
+
expect(Object.keys(Validator.getReports()).length).toBe(0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
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 MissingHrefOnA extends AbstractRule {
|
|
10
|
+
protected selector: string = 'a:not([href])';
|
|
11
|
+
|
|
12
|
+
protected ruleConfig: IAbstractRuleConfig = {
|
|
13
|
+
id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.missing_href_on_a),
|
|
14
|
+
links: [
|
|
15
|
+
{
|
|
16
|
+
content: 'F42: Failure of Success Criteria 1.3.1, 2.1.1, 2.1.3, or 4.1.2 when emulating links',
|
|
17
|
+
url: 'https://www.w3.org/TR/WCAG20-TECHS/F42.html'
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
recommendations: [],
|
|
21
|
+
severity: $severity.critical,
|
|
22
|
+
type: CATEGORY_TYPE.BEST_PRACTICE
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
public validate(anchorElements: HTMLAnchorElement[]): void {
|
|
26
|
+
const reportNodeWithoutHref = (anchorElement: HTMLAnchorElement): void => {
|
|
27
|
+
const reportMessage: string = TranslateService.instant('missing_href_on_a_report_message', [TextUtility.escape('<a>')]);
|
|
28
|
+
|
|
29
|
+
const report: IIssueReport = {
|
|
30
|
+
message: reportMessage,
|
|
31
|
+
node: anchorElement,
|
|
32
|
+
ruleId: this.ruleConfig.id
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
this.validator.report(report);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
anchorElements.forEach(reportNodeWithoutHref);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { MisusedAriaOnFocusableElement } from './misused-aria-on-focusable-element';
|
|
2
|
+
|
|
3
|
+
describe('Rules', () => {
|
|
4
|
+
|
|
5
|
+
describe('MisusedAriaOnFocusableElement', () => {
|
|
6
|
+
|
|
7
|
+
it('should indicate that class exists', () => {
|
|
8
|
+
expect(MisusedAriaOnFocusableElement).toBeDefined();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
});
|
|
12
|
+
});
|