testaro 18.1.0 → 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 +2 -2
- 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,80 @@
|
|
|
1
|
+
import { DomUtility } from '../../../utils/dom';
|
|
2
|
+
import { Validator } from '../../../validator';
|
|
3
|
+
import { EmptyTitleAttribute } from './empty-title-attribute';
|
|
4
|
+
|
|
5
|
+
describe('Rules', () => {
|
|
6
|
+
|
|
7
|
+
describe('EmptyTitleAttribute', () => {
|
|
8
|
+
|
|
9
|
+
const selector: string = `[title]${[
|
|
10
|
+
':not(img)',
|
|
11
|
+
':not(html)',
|
|
12
|
+
':not(head)',
|
|
13
|
+
':not(title)',
|
|
14
|
+
':not(body)',
|
|
15
|
+
':not(link)',
|
|
16
|
+
':not(meta)',
|
|
17
|
+
':not(title)',
|
|
18
|
+
':not(style)',
|
|
19
|
+
':not(script)',
|
|
20
|
+
':not(noscript)',
|
|
21
|
+
':not(iframe)',
|
|
22
|
+
':not(br)',
|
|
23
|
+
':not(hr)'
|
|
24
|
+
].join('')}`;
|
|
25
|
+
|
|
26
|
+
let fakeDom;
|
|
27
|
+
|
|
28
|
+
new EmptyTitleAttribute().registerValidator();
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
fakeDom = document.createElement('div');
|
|
32
|
+
fakeDom.id = 'fakedom';
|
|
33
|
+
document.body.appendChild(fakeDom);
|
|
34
|
+
|
|
35
|
+
Validator.reset();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
DomUtility.remove(document.getElementById('fakedom'));
|
|
40
|
+
fakeDom = undefined;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should return one report when there is an element with defined an attribute title with an empty value', () => {
|
|
44
|
+
fakeDom.innerHTML = '<div title="">test</div>';
|
|
45
|
+
|
|
46
|
+
const nodes = DomUtility.querySelectorAllExclude(selector, fakeDom);
|
|
47
|
+
|
|
48
|
+
new EmptyTitleAttribute().validate(nodes);
|
|
49
|
+
|
|
50
|
+
expect(Object.keys(Validator.getReports()).length).toBe(1);
|
|
51
|
+
expect(Validator.getReport('report_0').message).toBe('You have an attribute <code>title</code> with an empty content.');
|
|
52
|
+
expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('div');
|
|
53
|
+
expect(Validator.getReport('report_0').ruleId).toBe('empty-title-attribute');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should return one report when there is an element with defined an attribute title with an empty value (only spaces)', () => {
|
|
57
|
+
fakeDom.innerHTML = '<div title=" ">test</div>';
|
|
58
|
+
|
|
59
|
+
const nodes = DomUtility.querySelectorAllExclude(selector, fakeDom);
|
|
60
|
+
|
|
61
|
+
new EmptyTitleAttribute().validate(nodes);
|
|
62
|
+
|
|
63
|
+
expect(Object.keys(Validator.getReports()).length).toBe(1);
|
|
64
|
+
expect(Validator.getReport('report_0').message).toBe('You have an attribute <code>title</code> with an empty content.');
|
|
65
|
+
expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('div');
|
|
66
|
+
expect(Validator.getReport('report_0').ruleId).toBe('empty-title-attribute');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should return no reports when there is an element with attribute title and non-empty value', () => {
|
|
70
|
+
fakeDom.innerHTML = '<div title="some title">test</div>';
|
|
71
|
+
|
|
72
|
+
const nodes = DomUtility.querySelectorAllExclude(selector, fakeDom);
|
|
73
|
+
|
|
74
|
+
new EmptyTitleAttribute().validate(nodes);
|
|
75
|
+
|
|
76
|
+
expect(Object.keys(Validator.getReports()).length).toBe(0);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
|
|
8
|
+
export class EmptyTitleAttribute extends AbstractRule {
|
|
9
|
+
protected selector: string = `[title]${[
|
|
10
|
+
':not(img)',
|
|
11
|
+
':not(html)',
|
|
12
|
+
':not(head)',
|
|
13
|
+
':not(title)',
|
|
14
|
+
':not(body)',
|
|
15
|
+
':not(link)',
|
|
16
|
+
':not(meta)',
|
|
17
|
+
':not(title)',
|
|
18
|
+
':not(style)',
|
|
19
|
+
':not(script)',
|
|
20
|
+
':not(noscript)',
|
|
21
|
+
':not(iframe)',
|
|
22
|
+
':not(br)',
|
|
23
|
+
':not(hr)'
|
|
24
|
+
].join('')}`;
|
|
25
|
+
|
|
26
|
+
protected ruleConfig: IAbstractRuleConfig = {
|
|
27
|
+
id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.empty_title_attribute),
|
|
28
|
+
links: [
|
|
29
|
+
{
|
|
30
|
+
content: 'H67: Using null alt text and no title attribute on img elements for images that AT should ignore',
|
|
31
|
+
url: 'https://www.w3.org/TR/WCAG20-TECHS/H67.html'
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
recommendations: [],
|
|
35
|
+
severity: $severity.low,
|
|
36
|
+
type: CATEGORY_TYPE.BEST_PRACTICE
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
public validate(elements: Element[]): void {
|
|
40
|
+
const reportEmptyTitle = (element: Element): void => {
|
|
41
|
+
const titleAttribute: string | null = element.getAttribute('title');
|
|
42
|
+
|
|
43
|
+
if (titleAttribute === null || titleAttribute.trim().length > 0) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const report: IIssueReport = {
|
|
48
|
+
message: TranslateService.instant('empty_title_attribute_report_message'),
|
|
49
|
+
node: element,
|
|
50
|
+
ruleId: this.ruleConfig.id
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
this.validator.report(report);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
elements.forEach(reportEmptyTitle);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# flash-content
|
|
2
|
+
|
|
3
|
+
## Rule id
|
|
4
|
+
|
|
5
|
+
`flash-content`
|
|
6
|
+
|
|
7
|
+
## Definition
|
|
8
|
+
|
|
9
|
+
This rule verifies if there are Adobe Flash Player components.
|
|
10
|
+
|
|
11
|
+
## Purpose
|
|
12
|
+
|
|
13
|
+
> Since Adobe no longer supports Flash Player after December 31, 2020 and blocked Flash content from running in Flash Player beginning January 12, 2021, Adobe strongly recommends all users immediately uninstall Flash Player to help protect their systems.
|
|
14
|
+
|
|
15
|
+
Source: https://www.adobe.com/products/flashplayer/end-of-life.html
|
|
16
|
+
|
|
17
|
+
Due to above security reasons and poor an accessibility support Adobe Flash player should be removed.
|
|
18
|
+
|
|
19
|
+
Following elements are being considered as Adobe Flash Player while evaluating this rule:
|
|
20
|
+
|
|
21
|
+
CSS selector:
|
|
22
|
+
|
|
23
|
+
[classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"], embed[type="application/x-shockwave-flash"], object[type="application/x-shockwave-flash"]
|
|
24
|
+
|
|
25
|
+
## Test cases
|
|
26
|
+
|
|
27
|
+
### Passed
|
|
28
|
+
|
|
29
|
+
The rule passes when there are no elements that refers to Adobe Flash Player.
|
|
30
|
+
|
|
31
|
+
## WCAG Success Criteria
|
|
32
|
+
|
|
33
|
+
Not Applicable
|
|
34
|
+
|
|
35
|
+
## Best Practice
|
|
36
|
+
|
|
37
|
+
Yes
|
|
38
|
+
|
|
39
|
+
## User Impact
|
|
40
|
+
|
|
41
|
+
* **Severity**: low
|
|
42
|
+
* **Disabilities Affected**:
|
|
43
|
+
* Visual:
|
|
44
|
+
* blindness
|
|
45
|
+
|
|
46
|
+
## Resources
|
|
47
|
+
|
|
48
|
+
* https://www.adobe.com/products/flashplayer/end-of-life.html
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { FlashContent } from './flash-content';
|
|
2
|
+
import { DomUtility } from '../../../utils/dom';
|
|
3
|
+
import { Validator } from '../../../validator';
|
|
4
|
+
|
|
5
|
+
describe('Rules', () => {
|
|
6
|
+
|
|
7
|
+
describe('#flash-content', () => {
|
|
8
|
+
|
|
9
|
+
let fakeDom;
|
|
10
|
+
|
|
11
|
+
new FlashContent().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 one report for one flash object', () => {
|
|
27
|
+
const objectEl = document.createElement('object');
|
|
28
|
+
|
|
29
|
+
objectEl.setAttribute('classid', 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000');
|
|
30
|
+
objectEl.setAttribute('codebase', 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,19,0');
|
|
31
|
+
fakeDom.appendChild(objectEl);
|
|
32
|
+
|
|
33
|
+
const nodes = DomUtility.querySelectorAllExclude('[classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"]', fakeDom);
|
|
34
|
+
|
|
35
|
+
new FlashContent().validate(nodes);
|
|
36
|
+
|
|
37
|
+
expect(Object.keys(Validator.getReports()).length).toBe(1);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should return no reports where there are no flash objects', () => {
|
|
41
|
+
fakeDom.innerHTML = '<a href="#">test</a>';
|
|
42
|
+
|
|
43
|
+
const nodes = DomUtility.querySelectorAllExclude('[classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"]', fakeDom);
|
|
44
|
+
|
|
45
|
+
new FlashContent().validate(nodes);
|
|
46
|
+
|
|
47
|
+
expect(Object.keys(Validator.getReports()).length).toBe(0);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
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 FlashContent extends AbstractRule {
|
|
10
|
+
protected selector: string = '[classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"], embed[type="application/x-shockwave-flash"], object[type="application/x-shockwave-flash"]';
|
|
11
|
+
protected ruleConfig: IAbstractRuleConfig = {
|
|
12
|
+
id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.flash_content),
|
|
13
|
+
links: [],
|
|
14
|
+
recommendations: [],
|
|
15
|
+
severity: $severity.critical,
|
|
16
|
+
type: CATEGORY_TYPE.BEST_PRACTICE
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
public validate(elements: Element[]): void {
|
|
20
|
+
const reportFlash = (element: Element): void => {
|
|
21
|
+
const problem: IIssueReport = {
|
|
22
|
+
message: TranslateService.instant('flash_content_report_message'),
|
|
23
|
+
node: element,
|
|
24
|
+
ruleId: this.ruleConfig.id
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
this.validator.report(problem);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
elements.forEach(reportFlash);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# font-style-italic
|
|
2
|
+
|
|
3
|
+
## Rule id
|
|
4
|
+
|
|
5
|
+
`font-style-italic`
|
|
6
|
+
|
|
7
|
+
## Definition
|
|
8
|
+
|
|
9
|
+
This rule verifies if there is defined `font-style` type `italic` for a text with length > 80 chars.
|
|
10
|
+
|
|
11
|
+
## Purpose
|
|
12
|
+
|
|
13
|
+
[WCAG Understanding Guideline 3.1](https://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning.html) includes an advisory technique for "avoiding chunks of italic text". Similarly [WebAIM advises](https://webaim.org/articles/evaluatingcognitive/#readability) as follows: "Do not use italics or bold on long sections of text", but at the same time "use various stylistic elements (italics, bold, color, brief animation, or differently-styled content) to highlight important content".
|
|
14
|
+
|
|
15
|
+
This rule checks 2 cases:
|
|
16
|
+
|
|
17
|
+
* Determine reasonable text length - must be < 80 chars.
|
|
18
|
+
* Determine if element has defined `font-style` type `italic`.
|
|
19
|
+
|
|
20
|
+
## Test cases
|
|
21
|
+
|
|
22
|
+
### Passed
|
|
23
|
+
|
|
24
|
+
The rule passes when specified element contains text with length > 80 chars and have no defined style `font-style` type `italic`.
|
|
25
|
+
|
|
26
|
+
## WCAG Success Criteria
|
|
27
|
+
|
|
28
|
+
Not Applicable
|
|
29
|
+
|
|
30
|
+
## Best Practice
|
|
31
|
+
|
|
32
|
+
Yes
|
|
33
|
+
|
|
34
|
+
## User Impact
|
|
35
|
+
|
|
36
|
+
* **Severity**: low
|
|
37
|
+
* **Disabilities Affected**:
|
|
38
|
+
* Visual:
|
|
39
|
+
* blindness
|
|
40
|
+
|
|
41
|
+
## Resources
|
|
42
|
+
|
|
43
|
+
* https://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning.html
|
|
44
|
+
* https://webaim.org/articles/evaluatingcognitive/#readability
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { DomUtility } from '../../../utils/dom';
|
|
2
|
+
import { Css } from '../../../utils/css';
|
|
3
|
+
import { TextUtility } from '../../../utils/text';
|
|
4
|
+
import { CATEGORY_TYPE } from '../../../constants/categoryType';
|
|
5
|
+
import { IIssueReport } from '../../../interfaces/rule-issue.interface';
|
|
6
|
+
import { TranslateService } from '../../../services/translate';
|
|
7
|
+
import { $accessibilityAuditRules, $severity } from '../../../constants/accessibility';
|
|
8
|
+
import { AbstractRule, IAbstractRuleConfig } from '../../abstract-rule';
|
|
9
|
+
|
|
10
|
+
export class FontStyleItalic extends AbstractRule {
|
|
11
|
+
|
|
12
|
+
protected selector: string = `*${[
|
|
13
|
+
':root',
|
|
14
|
+
'head',
|
|
15
|
+
'style',
|
|
16
|
+
'script',
|
|
17
|
+
'noscript',
|
|
18
|
+
'meta',
|
|
19
|
+
'link',
|
|
20
|
+
'br',
|
|
21
|
+
'hr',
|
|
22
|
+
'object',
|
|
23
|
+
'svg',
|
|
24
|
+
'path',
|
|
25
|
+
'defs',
|
|
26
|
+
'rect',
|
|
27
|
+
'clippath',
|
|
28
|
+
'use',
|
|
29
|
+
'g',
|
|
30
|
+
'b',
|
|
31
|
+
'filter',
|
|
32
|
+
'img',
|
|
33
|
+
'picture',
|
|
34
|
+
'input',
|
|
35
|
+
'iframe',
|
|
36
|
+
'code',
|
|
37
|
+
'metadata',
|
|
38
|
+
':empty'
|
|
39
|
+
].map((i: string): string => {
|
|
40
|
+
return `:not(${i})`;
|
|
41
|
+
}).join('')}`;
|
|
42
|
+
|
|
43
|
+
protected ruleConfig: IAbstractRuleConfig = {
|
|
44
|
+
id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.font_style_italic),
|
|
45
|
+
links: [
|
|
46
|
+
{
|
|
47
|
+
content: 'Avoiding chunks of italic text',
|
|
48
|
+
url: 'https://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning.html'
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
recommendations: [],
|
|
52
|
+
severity: $severity.info,
|
|
53
|
+
type: CATEGORY_TYPE.BEST_PRACTICE
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
public validate(elements: HTMLElement[]): void {
|
|
57
|
+
const reportNode = (element: HTMLElement): void => {
|
|
58
|
+
let isItalic: boolean = false;
|
|
59
|
+
const textContent: string = DomUtility.getTextFromDescendantContent(element).trim();
|
|
60
|
+
const textContentLength: number = textContent.length;
|
|
61
|
+
const REASONABLE_LONG_TEXT: number = 80;
|
|
62
|
+
|
|
63
|
+
// Note: element.style may not exists, e.g. for an element in a different namespace
|
|
64
|
+
if (Css.getStyle(element, 'font-style') === 'italic' || typeof element.style === 'object' && element.style.fontStyle === 'italic') {
|
|
65
|
+
isItalic = true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (isItalic === false || textContentLength < REASONABLE_LONG_TEXT) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const report: IIssueReport = {
|
|
73
|
+
message: TranslateService.instant('font_style_italic_report_message', [textContentLength]),
|
|
74
|
+
node: element,
|
|
75
|
+
ruleId: this.ruleConfig.id
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
this.validator.report(report);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
elements.forEach(reportNode);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# h1-must-be
|
|
2
|
+
|
|
3
|
+
## Rule id
|
|
4
|
+
|
|
5
|
+
`h1-must-be`
|
|
6
|
+
|
|
7
|
+
## Definition
|
|
8
|
+
|
|
9
|
+
This rule verifies if there is defined heading `h1` on the page.
|
|
10
|
+
|
|
11
|
+
## Purpose
|
|
12
|
+
|
|
13
|
+
Most content on web pages should be organized into sections. When pages are organized into sections, a heading should be present.
|
|
14
|
+
|
|
15
|
+
All pages should at least have a `<h1>` level heading giving the title of the page.
|
|
16
|
+
|
|
17
|
+
This rule checks 1 case:
|
|
18
|
+
|
|
19
|
+
* Determine if there is at least 1 element `<h1>`.
|
|
20
|
+
|
|
21
|
+
**Note**: the rule does not check if the content of `<h1>` is empty.
|
|
22
|
+
|
|
23
|
+
## Test cases
|
|
24
|
+
|
|
25
|
+
### Passed
|
|
26
|
+
|
|
27
|
+
The rule passes when there is at least 1 `<h1>` element.
|
|
28
|
+
|
|
29
|
+
## WCAG Success Criteria
|
|
30
|
+
|
|
31
|
+
Not Applicable
|
|
32
|
+
|
|
33
|
+
## Best Practice
|
|
34
|
+
|
|
35
|
+
Yes
|
|
36
|
+
|
|
37
|
+
## User Impact
|
|
38
|
+
|
|
39
|
+
* **Severity**: critical
|
|
40
|
+
* **Disabilities Affected**:
|
|
41
|
+
* Visual:
|
|
42
|
+
* blindness
|
|
43
|
+
|
|
44
|
+
## Resources
|
|
45
|
+
|
|
46
|
+
* https://www.w3.org/WAI/tutorials/page-structure/headings/
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { H1MustBe } from './h1-must-be';
|
|
2
|
+
import { Validator } from '../../../validator';
|
|
3
|
+
import { DomUtility } from '../../../utils/dom';
|
|
4
|
+
|
|
5
|
+
describe('Rules', () => {
|
|
6
|
+
|
|
7
|
+
describe('H1MustBe', () => {
|
|
8
|
+
|
|
9
|
+
let fakeDom;
|
|
10
|
+
|
|
11
|
+
new H1MustBe().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 one report when there is no elements with h1', () => {
|
|
27
|
+
fakeDom.innerHTML = '<div><h2>h1</h2></div>';
|
|
28
|
+
|
|
29
|
+
new H1MustBe().run(fakeDom);
|
|
30
|
+
|
|
31
|
+
expect(Object.keys(Validator.getReports()).length).toBe(1);
|
|
32
|
+
expect(Validator.getReport('report_0').message).toBe('Expected at least one heading <code>h1</code> element, but found none.');
|
|
33
|
+
expect(Validator.getReport('report_0').node).toBe(null);
|
|
34
|
+
expect(Validator.getReport('report_0').ruleId).toBe('h1-must-be');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should return no reports when there is element with h1', () => {
|
|
38
|
+
fakeDom.innerHTML = '<div><h1>h1</h1></div>';
|
|
39
|
+
|
|
40
|
+
new H1MustBe().run(fakeDom);
|
|
41
|
+
|
|
42
|
+
expect(Object.keys(Validator.getReports()).length).toBe(0);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
|
|
8
|
+
export class H1MustBe extends AbstractRule {
|
|
9
|
+
protected selector: string = 'h1';
|
|
10
|
+
protected ruleConfig: IAbstractRuleConfig = {
|
|
11
|
+
id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.h1_must_be),
|
|
12
|
+
links: [
|
|
13
|
+
{
|
|
14
|
+
content: 'Web Accessibility Tutorials: Headings',
|
|
15
|
+
url: 'https://www.w3.org/WAI/tutorials/page-structure/headings/'
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
recommendations: [],
|
|
19
|
+
severity: $severity.critical,
|
|
20
|
+
type: CATEGORY_TYPE.BEST_PRACTICE
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
public validate(elements: Element[]): void {
|
|
24
|
+
if (elements.length > 0) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const report: IIssueReport = {
|
|
29
|
+
message: TranslateService.instant('h1_must_report_message'),
|
|
30
|
+
node: null,
|
|
31
|
+
ruleId: this.ruleConfig.id
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
this.validator.report(report);
|
|
35
|
+
}
|
|
36
|
+
}
|
package/aslint/app/rules/aslint/headings-sibling-unique/headings-sibling-unique.documentation.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# headings-sibling-unique
|
|
2
|
+
|
|
3
|
+
## Rule id
|
|
4
|
+
|
|
5
|
+
`headings-sibling-unique`
|
|
6
|
+
|
|
7
|
+
## Definition
|
|
8
|
+
|
|
9
|
+
This rule verifies if the accessible names of sibling heading elements of the same level are unique.
|
|
10
|
+
|
|
11
|
+
## Purpose
|
|
12
|
+
|
|
13
|
+
If section headings that share the same parent heading are not unique, users of assistive technologies will not be able to discern the differences among sibling sections of the web page.
|
|
14
|
+
|
|
15
|
+
This rule checks 1 case:
|
|
16
|
+
|
|
17
|
+
* Sibling headings accessible names must be unique.
|
|
18
|
+
|
|
19
|
+
### Example
|
|
20
|
+
|
|
21
|
+
#### Incorrect
|
|
22
|
+
|
|
23
|
+
<h2>Example</h2>
|
|
24
|
+
<h2>Example</h2>
|
|
25
|
+
|
|
26
|
+
#### Correct
|
|
27
|
+
|
|
28
|
+
<h2>Example 1</h2>
|
|
29
|
+
<h2>Example 2</h2>
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## Test cases
|
|
33
|
+
|
|
34
|
+
### Passed
|
|
35
|
+
|
|
36
|
+
The rule passes when sibiling headings are having unique accessible name.
|
|
37
|
+
|
|
38
|
+
## WCAG Success Criteria
|
|
39
|
+
|
|
40
|
+
Not Applicable
|
|
41
|
+
|
|
42
|
+
## Best Practice
|
|
43
|
+
|
|
44
|
+
Yes
|
|
45
|
+
|
|
46
|
+
## User Impact
|
|
47
|
+
|
|
48
|
+
* **Severity**: critical
|
|
49
|
+
* **Disabilities Affected**:
|
|
50
|
+
* Visual:
|
|
51
|
+
* blindness
|
|
52
|
+
|
|
53
|
+
## Resources
|
|
54
|
+
|
|
55
|
+
* http://www.w3.org/TR/WCAG20/#navigation-mechanisms-descriptive
|
|
56
|
+
* http://www.w3.org/TR/WCAG20-TECHS/G130
|
|
57
|
+
* https://www.w3.org/TR/WCAG20-TECHS/G141.html
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { DomUtility } from '../../../utils/dom';
|
|
2
|
+
import { Validator } from '../../../validator';
|
|
3
|
+
import { HeadingsSiblingUnique } from './headings-sibling-unique';
|
|
4
|
+
|
|
5
|
+
describe('Rules', () => {
|
|
6
|
+
|
|
7
|
+
describe('HeadingsSiblingUnique', () => {
|
|
8
|
+
|
|
9
|
+
it('should indicate that class exists', () => {
|
|
10
|
+
expect(HeadingsSiblingUnique).toBeDefined();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
let fakeDom;
|
|
14
|
+
|
|
15
|
+
new HeadingsSiblingUnique().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 are non-unique siblings headings', () => {
|
|
31
|
+
fakeDom.innerHTML = '<h1>Test</h1><h1>Test</h1>';
|
|
32
|
+
const nodes = DomUtility.querySelectorAllExclude('h1', fakeDom) as HTMLHeadingElement[];
|
|
33
|
+
|
|
34
|
+
new HeadingsSiblingUnique().validate(nodes);
|
|
35
|
+
|
|
36
|
+
expect(Object.keys(Validator.getReports()).length).toBe(1);
|
|
37
|
+
expect(Validator.getReport('report_0').message).toBe('The accessible names of sibling heading elements of the same level are not unique. If section headings that share the same parent heading are not unique, users of assistive technologies will not be able to discern the differences among sibling sections of the web page. Same level <code>h1</code> and same description: <q>Test</q>.');
|
|
38
|
+
expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('h1');
|
|
39
|
+
expect(Validator.getReport('report_0').ruleId).toBe('headings-sibling-unique');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should return no reports when two headings description are different', () => {
|
|
43
|
+
fakeDom.innerHTML = '<h1>Test</h1><h1>Test 2</h1>';
|
|
44
|
+
const nodes = DomUtility.querySelectorAllExclude('h1', fakeDom) as HTMLHeadingElement[];
|
|
45
|
+
|
|
46
|
+
new HeadingsSiblingUnique().validate(nodes);
|
|
47
|
+
|
|
48
|
+
expect(Object.keys(Validator.getReports()).length).toBe(0);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
});
|
|
52
|
+
});
|