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.
Files changed (88) hide show
  1. package/aslint/LICENSE +362 -0
  2. package/aslint/README.md +260 -0
  3. package/aslint/app/rules/abstract-rule.ts +83 -0
  4. package/aslint/app/rules/aslint/aria-hidden-false/aria-hidden-false.test.ts +73 -0
  5. package/aslint/app/rules/aslint/aria-hidden-false/aria-hidden-false.ts +34 -0
  6. package/aslint/app/rules/aslint/aria-hidden-false/aria-hidden.documentation.md +32 -0
  7. package/aslint/app/rules/aslint/aria-role-dialog/aria-role-dialog.documentation.md +49 -0
  8. package/aslint/app/rules/aslint/aria-role-dialog/aria-role-dialog.test.ts +91 -0
  9. package/aslint/app/rules/aslint/aria-role-dialog/aria-role-dialog.ts +62 -0
  10. package/aslint/app/rules/aslint/capital-letters-words/capital-letters-words.documentation.md +44 -0
  11. package/aslint/app/rules/aslint/capital-letters-words/capital-letters-words.test.ts +111 -0
  12. package/aslint/app/rules/aslint/capital-letters-words/capital-letters-words.ts +120 -0
  13. package/aslint/app/rules/aslint/content-editable-missing-attributes/content-editable-missing-attributes.docmentation.md +48 -0
  14. package/aslint/app/rules/aslint/content-editable-missing-attributes/content-editable-missing-attributes.test.ts +67 -0
  15. package/aslint/app/rules/aslint/content-editable-missing-attributes/content-editable-missing-attributes.ts +63 -0
  16. package/aslint/app/rules/aslint/contentinfo-landmark-only-one/contentinfo-landmark-only-one.documentation.md +45 -0
  17. package/aslint/app/rules/aslint/contentinfo-landmark-only-one/contentinfo-landmark-only-one.test.ts +63 -0
  18. package/aslint/app/rules/aslint/contentinfo-landmark-only-one/contentinfo-landmark-only-one.ts +44 -0
  19. package/aslint/app/rules/aslint/elements-not-allowed-in-head/elements-not-allowed-in-head.documentation.md +65 -0
  20. package/aslint/app/rules/aslint/elements-not-allowed-in-head/elements-not-allowed-in-head.test.ts +53 -0
  21. package/aslint/app/rules/aslint/elements-not-allowed-in-head/elements-not-allowed-in-head.ts +47 -0
  22. package/aslint/app/rules/aslint/empty-title-attribute/empty-title-attribute.documentation.md +55 -0
  23. package/aslint/app/rules/aslint/empty-title-attribute/empty-title-attribute.test.ts +80 -0
  24. package/aslint/app/rules/aslint/empty-title-attribute/empty-title-attribute.ts +58 -0
  25. package/aslint/app/rules/aslint/flash-content/flash-content.documentation.md +48 -0
  26. package/aslint/app/rules/aslint/flash-content/flash-content.test.ts +52 -0
  27. package/aslint/app/rules/aslint/flash-content/flash-content.ts +32 -0
  28. package/aslint/app/rules/aslint/font-style-italic/font-style-italic.documentation.md +44 -0
  29. package/aslint/app/rules/aslint/font-style-italic/font-style-italic.test.ts +12 -0
  30. package/aslint/app/rules/aslint/font-style-italic/font-style-italic.ts +83 -0
  31. package/aslint/app/rules/aslint/h1-must-be/h1-must-be.documentation.md +46 -0
  32. package/aslint/app/rules/aslint/h1-must-be/h1-must-be.test.ts +46 -0
  33. package/aslint/app/rules/aslint/h1-must-be/h1-must-be.ts +36 -0
  34. package/aslint/app/rules/aslint/headings-sibling-unique/headings-sibling-unique.documentation.md +57 -0
  35. package/aslint/app/rules/aslint/headings-sibling-unique/headings-sibling-unique.test.ts +52 -0
  36. package/aslint/app/rules/aslint/headings-sibling-unique/headings-sibling-unique.ts +63 -0
  37. package/aslint/app/rules/aslint/horizontal-rule/horizontal-rule.documentation.md +39 -0
  38. package/aslint/app/rules/aslint/horizontal-rule/horizontal-rule.test.ts +66 -0
  39. package/aslint/app/rules/aslint/horizontal-rule/horizontal-rule.ts +37 -0
  40. package/aslint/app/rules/aslint/incorrect-technique-for-hiding-content/incorrect-technique-for-hiding-content.documentation.md +36 -0
  41. package/aslint/app/rules/aslint/incorrect-technique-for-hiding-content/incorrect-technique-for-hiding-content.test.ts +113 -0
  42. package/aslint/app/rules/aslint/incorrect-technique-for-hiding-content/incorrect-technique-for-hiding-content.ts +103 -0
  43. package/aslint/app/rules/aslint/invalid-attribute-dir-value/invalid-attribute-dir-value.documentation.md +34 -0
  44. package/aslint/app/rules/aslint/invalid-attribute-dir-value/invalid-attribute-dir-value.test.ts +82 -0
  45. package/aslint/app/rules/aslint/invalid-attribute-dir-value/invalid-attribute-dir-value.ts +44 -0
  46. package/aslint/app/rules/aslint/label-duplicated-content-title/label-duplicated-content-title.documentation.md +40 -0
  47. package/aslint/app/rules/aslint/label-duplicated-content-title/label-duplicated-content-title.test.ts +48 -0
  48. package/aslint/app/rules/aslint/label-duplicated-content-title/label-duplicated-content-title.ts +37 -0
  49. package/aslint/app/rules/aslint/links-language-destination/links-language-destination.test.ts +50 -0
  50. package/aslint/app/rules/aslint/links-language-destination/links-language-destination.ts +70 -0
  51. package/aslint/app/rules/aslint/main-element-only-one/main-element-only-one.test.ts +55 -0
  52. package/aslint/app/rules/aslint/main-element-only-one/main-element-only-one.ts +83 -0
  53. package/aslint/app/rules/aslint/main-landmark-must-be-top-level/main-landmark-must-be-top-level.test.ts +12 -0
  54. package/aslint/app/rules/aslint/main-landmark-must-be-top-level/main-landmark-must-be-top-level.ts +73 -0
  55. package/aslint/app/rules/aslint/minimum-font-size/minimum-font-size.test.ts +12 -0
  56. package/aslint/app/rules/aslint/minimum-font-size/minimum-font-size.ts +87 -0
  57. package/aslint/app/rules/aslint/missing-href-on-a/missing-href-on-a.test.ts +48 -0
  58. package/aslint/app/rules/aslint/missing-href-on-a/missing-href-on-a.ts +40 -0
  59. package/aslint/app/rules/aslint/misused-aria-on-focusable-element/misused-aria-on-focusable-element.test.ts +12 -0
  60. package/aslint/app/rules/aslint/misused-aria-on-focusable-element/misused-aria-on-focusable-element.ts +66 -0
  61. package/aslint/app/rules/aslint/misused-input-attribute/misused-input-attribute.test.ts +12 -0
  62. package/aslint/app/rules/aslint/misused-input-attribute/misused-input-attribute.ts +134 -0
  63. package/aslint/app/rules/aslint/misused-required-attribute/misused-required-attribute.test.ts +12 -0
  64. package/aslint/app/rules/aslint/misused-required-attribute/misused-required-attribute.ts +90 -0
  65. package/aslint/app/rules/aslint/navigation-landmark-restrictions/navigation-landmark-restrictions.test.ts +12 -0
  66. package/aslint/app/rules/aslint/navigation-landmark-restrictions/navigation-landmark-restrictions.ts +48 -0
  67. package/aslint/app/rules/aslint/obsolete-html-attributes/obsolete-html-attributes.test.ts +12 -0
  68. package/aslint/app/rules/aslint/obsolete-html-attributes/obsolete-html-attributes.ts +148 -0
  69. package/aslint/app/rules/aslint/obsolete-html-elements/obsolete-html-elements.test.ts +12 -0
  70. package/aslint/app/rules/aslint/obsolete-html-elements/obsolete-html-elements.ts +66 -0
  71. package/aslint/app/rules/aslint/outline-zero/outline-zero.test.ts +12 -0
  72. package/aslint/app/rules/aslint/outline-zero/outline-zero.ts +85 -0
  73. package/aslint/app/rules/aslint/overlay/overlay.test.ts +122 -0
  74. package/aslint/app/rules/aslint/overlay/overlay.ts +141 -0
  75. package/aslint/app/rules/aslint/role-application/role-application.test.ts +48 -0
  76. package/aslint/app/rules/aslint/role-application/role-application.ts +38 -0
  77. package/aslint/app/rules/aslint/rtl-content/rtl-content.test.ts +12 -0
  78. package/aslint/app/rules/aslint/rtl-content/rtl-content.ts +75 -0
  79. package/aslint/app/rules/aslint/unclear-uri-on-a/unclear-anchor-uri.test.ts +12 -0
  80. package/aslint/app/rules/aslint/unclear-uri-on-a/unclear-anchor-uri.ts +48 -0
  81. package/aslint/app/rules/aslint/unsupported-role-on-element/unsupported-role-on-element.test.ts +12 -0
  82. package/aslint/app/rules/aslint/unsupported-role-on-element/unsupported-role-on-element.ts +63 -0
  83. package/package.json +2 -2
  84. package/testaro/headingAmb.js +103 -0
  85. package/testaro/template.js +78 -0
  86. package/tests/testaro.js +1 -0
  87. package/validation/tests/jobs/headingAmb.json +129 -0
  88. package/validation/tests/targets/headingAmb/index.html +26 -0
@@ -0,0 +1,120 @@
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 { $severity } from '../../../constants/accessibility';
8
+ import { $accessibilityAuditRules } from '../../../constants/accessibility';
9
+ import { AbstractRule, IAbstractRuleConfig } from '../../abstract-rule';
10
+
11
+ export class CapitalLettersWords extends AbstractRule {
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.capital_letters_words),
45
+ links: [
46
+ {
47
+ content: 'Dyslexia Font and Style Guide',
48
+ url: 'https://www.dyslexia-reading-well.com/dyslexia-font.html'
49
+ },
50
+ {
51
+ content: 'Typefaces for dyslexia',
52
+ url: 'https://bdatech.org/what-technology/typefaces-for-dyslexia/'
53
+ }
54
+ ],
55
+ recommendations: [],
56
+ severity: $severity.high,
57
+ type: CATEGORY_TYPE.BEST_PRACTICE
58
+ };
59
+
60
+ public validate(nodes: HTMLElement[]): void {
61
+ const onlyWordsWithUpperCases = (word: string): boolean => {
62
+ return TextUtility.isUpperCase(word);
63
+ };
64
+
65
+ const processNode = (element: HTMLElement): void => {
66
+ const report: IIssueReport = {
67
+ message: '',
68
+ node: element,
69
+ ruleId: this.ruleConfig.id
70
+ };
71
+
72
+ let reportMessage: string = '';
73
+
74
+ let words: string[];
75
+ let onlyWordsWithUpperCase: string[] = [];
76
+
77
+ const text: string = DomUtility.getTextFromDescendantContent(element).trim();
78
+
79
+ words = text.split(' ');
80
+
81
+ if (text.length > 0 && words.length > 0) {
82
+ if (Css.isCssTextTransformUsed(element)) {
83
+ reportMessage = TranslateService.instant('capital_letters_words_report_message1', [text.toUpperCase()]);
84
+ } else {
85
+
86
+ onlyWordsWithUpperCase = words.filter(onlyWordsWithUpperCases);
87
+
88
+ if (onlyWordsWithUpperCase.length > 1) {
89
+ reportMessage = TranslateService.instant('capital_letters_words_report_message2');
90
+ }
91
+ }
92
+ }
93
+
94
+ const titleAttribute: string | null = element.getAttribute('title');
95
+
96
+ if (typeof titleAttribute === 'string' && titleAttribute.trim().length > 0) {
97
+ words = titleAttribute.split(' ');
98
+ onlyWordsWithUpperCase = words.filter(onlyWordsWithUpperCases);
99
+
100
+ if (onlyWordsWithUpperCase.length > 1) {
101
+ if (reportMessage.length === 0) {
102
+ reportMessage = TranslateService.instant('capital_letters_words_report_message3', [titleAttribute]);
103
+ } else {
104
+ reportMessage = TranslateService.instant('capital_letters_words_report_message4', [titleAttribute]);
105
+ }
106
+ }
107
+ }
108
+
109
+ if (reportMessage.length === 0) {
110
+ return;
111
+ }
112
+
113
+ report.message = `${reportMessage} ${TranslateService.instant('capital_letters_words_report_explanation')}`;
114
+
115
+ this.validator.report(report);
116
+ };
117
+
118
+ nodes.forEach(processNode);
119
+ }
120
+ }
@@ -0,0 +1,48 @@
1
+ # content-editable-missing-attributes
2
+
3
+ ## Rule id
4
+
5
+ `content-editable-missing-attributes`
6
+
7
+ ## Definition
8
+
9
+ This rule verifies if element with attribute `contenteditable` have defined also following attributes:
10
+
11
+ 1. `aria-multiline`
12
+ 2. `aria-labelledby` or `aria-label`
13
+
14
+ ## Purpose
15
+
16
+ When `aria-multiline="true"` is set then Assistive Technologies informs the user that the textbox supports multi-line input, with the expectation that <kbd>Enter</kbd> or <kbd>Return</kbd> will create a line break rather than submitting the form.
17
+
18
+ `aria-label` here is recommended to specify a string to be used as the accessible label.
19
+
20
+ Eventually `aria-labelledby` can be used to specify the `id` of another element in the DOM as an element's label.
21
+
22
+ ## Test cases
23
+
24
+ ### Passed
25
+
26
+ The rule passes when all of the following cases are fulfilled:
27
+
28
+ 1. Attribute `contenteditable` is defined.
29
+ 2. `aria-label` or `aria-labelledby` is defined. The rule does not validate `aria-labelledby` ids if the referenced elements exists.
30
+
31
+ ## WCAG Success Criteria
32
+
33
+ Not Applicable
34
+
35
+ ## Best Practice
36
+
37
+ Yes
38
+
39
+ ## User Impact
40
+
41
+ * **Severity**: high
42
+ * **Disabilities Affected**:
43
+ * Visual:
44
+ * blindness
45
+
46
+ ## Resources
47
+
48
+ Not available
@@ -0,0 +1,67 @@
1
+ import { ContentEditableMissingAttributes } from './content-editable-missing-attributes';
2
+ import { DomUtility } from '../../../utils/dom';
3
+ import { Validator } from '../../../validator';
4
+
5
+ describe('Rules', () => {
6
+
7
+ describe('#content-editable-missing-attributes', () => {
8
+
9
+ let fakeDom;
10
+
11
+ new ContentEditableMissingAttributes().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 3 reports when there is an element with attribute [contenteditable] only', () => {
27
+ fakeDom.innerHTML = '<p contenteditable="true"></p>';
28
+
29
+ const nodes = DomUtility.querySelectorAllExclude('[contenteditable]', fakeDom);
30
+
31
+ new ContentEditableMissingAttributes().validate(nodes);
32
+
33
+ expect(Object.keys(Validator.getReports()).length).toBe(3);
34
+
35
+ expect(Validator.getReport('report_0').message).toBe('Missing attribute <code>role=\'textbox\'</code> on <code>&lt;p contenteditable&#x3D;&quot;true&quot;&gt;&lt;&#x2F;p&gt;</code>');
36
+ expect(Validator.getReport('report_1').message).toBe('Missing attribute <code>aria-multiline=\'true\'</code> on <code>&lt;p contenteditable&#x3D;&quot;true&quot;&gt;&lt;&#x2F;p&gt;</code>');
37
+ expect(Validator.getReport('report_2').message).toBe('Missing attribute <code>aria-labelledby</code> or <code>aria-label</code> on <code>&lt;p contenteditable&#x3D;&quot;true&quot;&gt;&lt;&#x2F;p&gt;</code>');
38
+ });
39
+
40
+ it('should return 2 reports when there is an element with attribute [contenteditable] and defined attribute aria-label', () => {
41
+ fakeDom.innerHTML = '<p contenteditable="true" aria-label="test"></p><span id="test">test</span>';
42
+
43
+ const nodes = DomUtility.querySelectorAllExclude('[contenteditable]', fakeDom);
44
+
45
+ new ContentEditableMissingAttributes().validate(nodes);
46
+
47
+ expect(Object.keys(Validator.getReports()).length).toBe(2);
48
+
49
+ expect(Validator.getReport('report_0').message).toBe('Missing attribute <code>role=\'textbox\'</code> on <code>&lt;p contenteditable&#x3D;&quot;true&quot; aria-label&#x3D;&quot;test&quot;&gt;&lt;&#x2F;p&gt;</code>');
50
+ expect(Validator.getReport('report_1').message).toBe('Missing attribute <code>aria-multiline=\'true\'</code> on <code>&lt;p contenteditable&#x3D;&quot;true&quot; aria-label&#x3D;&quot;test&quot;&gt;&lt;&#x2F;p&gt;</code>');
51
+ });
52
+
53
+ it('should return 1 report for missing attribute role="textbox" when there is an element with attribute [contenteditable], defined attribute aria-label and aria-multiline', () => {
54
+ fakeDom.innerHTML = '<p contenteditable="true" aria-label="test" aria-multiline="true"></p><span id="test">test</span>';
55
+
56
+ const nodes = DomUtility.querySelectorAllExclude('[contenteditable]', fakeDom);
57
+
58
+ new ContentEditableMissingAttributes().validate(nodes);
59
+
60
+ expect(Object.keys(Validator.getReports()).length).toBe(1);
61
+
62
+ expect(Validator.getReport('report_0').message).toBe('Missing attribute <code>role=\'textbox\'</code> on <code>&lt;p contenteditable&#x3D;&quot;true&quot; aria-label&#x3D;&quot;test&quot; aria-multiline&#x3D;&quot;true&quot;&gt;&lt;&#x2F;p&gt;</code>');
63
+ });
64
+
65
+ });
66
+
67
+ });
@@ -0,0 +1,63 @@
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 ContentEditableMissingAttributes extends AbstractRule {
10
+ protected selector: string = '[contenteditable]';
11
+ protected ruleConfig: IAbstractRuleConfig = {
12
+ id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.content_editable_missing_attributes),
13
+ links: [],
14
+ recommendations: [],
15
+ severity: $severity.low,
16
+ type: CATEGORY_TYPE.BEST_PRACTICE
17
+ };
18
+
19
+ public validate(elements: Element[]): void {
20
+ const reportMissingAttributes = (element: Element): void => {
21
+ let problem: IIssueReport;
22
+ const roleValue: string | null = element.getAttribute('role');
23
+
24
+ if (roleValue !== 'textbox') {
25
+ const reportMessage: string = TranslateService.instant('content_editable_missing_attributes_report_message1', [DomUtility.getEscapedOuterHTML(element)]);
26
+
27
+ problem = {
28
+ message: reportMessage,
29
+ node: element,
30
+ ruleId: this.ruleConfig.id
31
+ };
32
+
33
+ this.validator.report(problem);
34
+ }
35
+
36
+ if (element.getAttribute('aria-multiline') === null) {
37
+ const reportMessage: string = TranslateService.instant('content_editable_missing_attributes_report_message2', [DomUtility.getEscapedOuterHTML(element)]);
38
+
39
+ problem = {
40
+ message: reportMessage,
41
+ node: element,
42
+ ruleId: this.ruleConfig.id
43
+ };
44
+
45
+ this.validator.report(problem);
46
+ }
47
+
48
+ if (element.getAttribute('aria-labelledby') === null && element.getAttribute('aria-label') === null) {
49
+ const reportMessage: string = TranslateService.instant('content_editable_missing_attributes_report_message3', [DomUtility.getEscapedOuterHTML(element)]);
50
+
51
+ problem = {
52
+ message: reportMessage,
53
+ node: element,
54
+ ruleId: this.ruleConfig.id
55
+ };
56
+
57
+ this.validator.report(problem);
58
+ }
59
+ };
60
+
61
+ elements.forEach(reportMissingAttributes);
62
+ }
63
+ }
@@ -0,0 +1,45 @@
1
+ # contentinfo-landmark-only-one
2
+
3
+ ## Rule id
4
+
5
+ `contentinfo-landmark-only-one`
6
+
7
+ ## Definition
8
+
9
+ This rule verifies if there is no more than 1 element with an attribute `role="contentinfo"`.
10
+
11
+ ## Purpose
12
+
13
+ Based on https://www.w3.org/TR/wai-aria/#contentinfo
14
+
15
+ > Within any document or application, the author SHOULD mark no more than one element with the contentinfo role.
16
+
17
+
18
+ ## Test cases
19
+
20
+ ### Passed
21
+
22
+ The rule passes when all of the following cases are fulfilled:
23
+
24
+ 1. There is no more than 1 element with an attribute `role="contentinfo"`
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/wai-aria/#contentinfo
44
+ * http://www.w3.org/TR/WCAG20-TECHS/ARIA11
45
+
@@ -0,0 +1,63 @@
1
+ import { ContentinfoLandmarkOnlyOne } from './contentinfo-landmark-only-one';
2
+ import { DomUtility } from '../../../utils/dom';
3
+ import { Validator } from '../../../validator';
4
+
5
+ describe('Rules', () => {
6
+
7
+ describe('ContentinfoLandmarkOnlyOne', () => {
8
+
9
+ let fakeDom;
10
+
11
+ new ContentinfoLandmarkOnlyOne().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 an element with more than one attribute role="contentinfo"', () => {
27
+ fakeDom.innerHTML = '<div role="contentinfo"></div><div role="contentinfo"></div>';
28
+
29
+ const nodes = DomUtility.querySelectorAllExclude('[role=contentinfo]', fakeDom);
30
+
31
+ new ContentinfoLandmarkOnlyOne().validate(nodes);
32
+
33
+ expect(Object.keys(Validator.getReports()).length).toBe(2);
34
+ expect(Validator.getReport('report_0').message).toBe('Expected attribute <code>role&#x3D;&quot;contentinfo&quot;</code> to be defined only once. You have 2 .');
35
+ expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('div');
36
+ expect(Validator.getReport('report_0').ruleId).toBe('contentinfo-landmark-only-one');
37
+ expect(Validator.getReport('report_1').message).toBe('Expected attribute <code>role&#x3D;&quot;contentinfo&quot;</code> to be defined only once. You have 2 .');
38
+ expect(Validator.getReport('report_1').node.nodeName.toLowerCase()).toBe('div');
39
+ expect(Validator.getReport('report_1').ruleId).toBe('contentinfo-landmark-only-one');
40
+ });
41
+
42
+ it('should return no reports when there is 1 element with attribute role="contentinfo"', () => {
43
+ fakeDom.innerHTML = '<div role="contentinfo">';
44
+
45
+ const nodes = DomUtility.querySelectorAllExclude('[role=contentinfo]', fakeDom);
46
+
47
+ new ContentinfoLandmarkOnlyOne().validate(nodes);
48
+
49
+ expect(Object.keys(Validator.getReports()).length).toBe(0);
50
+ });
51
+
52
+ it('should return no reports when there is no elements with attribute role="contentinfo"', () => {
53
+ fakeDom.innerHTML = '<div>contentinfo</div>';
54
+ const nodes = DomUtility.querySelectorAllExclude('[role=contentinfo]', fakeDom);
55
+
56
+ new ContentinfoLandmarkOnlyOne().validate(nodes);
57
+
58
+ expect(Object.keys(Validator.getReports()).length).toBe(0);
59
+ });
60
+
61
+ });
62
+
63
+ });
@@ -0,0 +1,44 @@
1
+ import { TextUtility } from '../../../utils/text';
2
+ import { CATEGORY_TYPE } from '../../../constants/categoryType';
3
+ import { IIssueReport } from '../../../interfaces/rule-issue.interface';
4
+ import { $accessibilityAuditRules, $severity } from '../../../constants/accessibility';
5
+ import { TranslateService } from '../../../services/translate';
6
+ import { AbstractRule, IAbstractRuleConfig } from '../../abstract-rule';
7
+
8
+ export class ContentinfoLandmarkOnlyOne extends AbstractRule {
9
+ protected selector: string = '[role="contentinfo"]';
10
+ protected ruleConfig: IAbstractRuleConfig = {
11
+ id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.contentinfo_landmark_only_one),
12
+ links: [
13
+ {
14
+ content: 'ARIA11: Using ARIA landmarks to identify regions of a page',
15
+ url: 'http://www.w3.org/TR/WCAG20-TECHS/ARIA11'
16
+ },
17
+ {
18
+ content: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: contentinfo role',
19
+ url: 'https://www.w3.org/TR/wai-aria/#contentinfo'
20
+ }
21
+ ],
22
+ recommendations: [],
23
+ severity: $severity.low,
24
+ type: CATEGORY_TYPE.BEST_PRACTICE
25
+ };
26
+
27
+ public validate(nodes: Element[]): void {
28
+ const total: number = nodes.length;
29
+
30
+ if (total > 1) {
31
+ nodes.forEach((element: Element): void => {
32
+ const reportMessage: string = TranslateService.instant('contentinfo_landmark_only_one_report_message', [TextUtility.escape('role="contentinfo"'), total, '.']);
33
+
34
+ const report: IIssueReport = {
35
+ message: reportMessage,
36
+ node: element,
37
+ ruleId: this.ruleConfig.id
38
+ };
39
+
40
+ this.validator.report(report);
41
+ });
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,65 @@
1
+ # elements-not-allowed-in-head
2
+
3
+ ## Rule id
4
+
5
+ `elements-not-allowed-in-head`
6
+
7
+ ## Definition
8
+
9
+ This rule verifies if there are elements that shouldn't be in the `<head>` section.
10
+
11
+ ## Purpose
12
+
13
+ `<head>` section contains elements that provides information of how the document should be perceived, and rendered, by web technologies. e.g. browsers, search engines, bots, screen readers, etc.
14
+
15
+ Non valid pages rely on the browser to auto-correct your code, and each browser does this differently. If your code is valid the browser has to do less processing as it doesn't have to correct any code, therefore the page will render faster and more predictable.
16
+
17
+ In order to make the code accurate processed and rendered it is recommended to have only valid HTML elements / tags in the `<head>` section.
18
+
19
+ Following elements are allowed to be in the `<head>` section:
20
+
21
+ <title>
22
+ <base>
23
+ <link>
24
+ <style>
25
+ <meta>
26
+ <script>
27
+ <noscript>
28
+ <template>
29
+
30
+ All other elements / tags are perceived as non-valid.
31
+
32
+ ## Test cases
33
+
34
+ ### Passed
35
+
36
+ The rule passes when only following elements / tags are specified in the `<head>` section:
37
+
38
+ <title>
39
+ <base>
40
+ <link>
41
+ <style>
42
+ <meta>
43
+ <script>
44
+ <noscript>
45
+ <template>
46
+
47
+ ## WCAG Success Criteria
48
+
49
+ Not Applicable
50
+
51
+ ## Best Practice
52
+
53
+ Yes
54
+
55
+ ## User Impact
56
+
57
+ * **Severity**: low
58
+ * **Disabilities Affected**:
59
+ * Visual:
60
+ * blindness
61
+
62
+ ## Resources
63
+
64
+ * https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML
65
+ * https://html.spec.whatwg.org/multipage/semantics.html#the-head-element
@@ -0,0 +1,53 @@
1
+ import { ElementsNotAllowed } from './elements-not-allowed-in-head';
2
+ import { DomUtility } from '../../../utils/dom';
3
+ import { Validator } from '../../../validator';
4
+
5
+ describe('Rules', () => {
6
+
7
+ describe('ElementsNotAllowed#', () => {
8
+
9
+ let fakeDom;
10
+ const VALID_ELEMENTS: string[] = [':not(base)', ':not(link)', ':not(meta)', ':not(script)', ':not(style)', ':not(title)', ':not(noscript)', ':not(template)'];
11
+ const selector = `head *${VALID_ELEMENTS.join('')}`;
12
+
13
+ new ElementsNotAllowed().registerValidator();
14
+
15
+ beforeEach(() => {
16
+ Validator.reset();
17
+ });
18
+
19
+ afterEach(() => {
20
+ DomUtility.remove(document.getElementById('headfakedom'));
21
+ fakeDom = undefined;
22
+ });
23
+
24
+ it('should return one report when there is an element not supposed to be in head block', () => {
25
+ fakeDom = document.createElement('div');
26
+ fakeDom.id = 'headfakedom';
27
+ document.head.appendChild(fakeDom);
28
+
29
+ const nodes = DomUtility.querySelectorAllExclude(selector);
30
+
31
+ new ElementsNotAllowed().validate(nodes);
32
+
33
+ expect(Object.keys(Validator.getReports()).length).toBe(1);
34
+ expect(Validator.getReport('report_0').message).toBe('Expected <code>&lt;div id&#x3D;&quot;headfakedom&quot;&gt;&lt;&#x2F;div&gt;</code> not to be a child of <code>&lt;head&gt;</code>.');
35
+ expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('div');
36
+ expect(Validator.getReport('report_0').ruleId).toBe('elements-not-allowed-in-head');
37
+ });
38
+
39
+ it('should return no reports when there is no invalid elements in head section', () => {
40
+ fakeDom = document.createElement('noscript');
41
+ fakeDom.id = 'headfakedom';
42
+ document.head.appendChild(fakeDom);
43
+
44
+ const nodes = DomUtility.querySelectorAllExclude(selector);
45
+
46
+ new ElementsNotAllowed().validate(nodes);
47
+
48
+ expect(Object.keys(Validator.getReports()).length).toBe(0);
49
+ });
50
+
51
+ });
52
+
53
+ });
@@ -0,0 +1,47 @@
1
+ import { DomUtility } from '../../../utils/dom';
2
+ import { TextUtility } from '../../../utils/text';
3
+ import { CATEGORY_TYPE } from '../../../constants/categoryType';
4
+ import { IIssueReport } from '../../../interfaces/rule-issue.interface';
5
+ import { TranslateService } from '../../../services/translate';
6
+ import { $accessibilityAuditRules, $severity } from '../../../constants/accessibility';
7
+ import { AbstractRule, IAbstractRuleConfig } from '../../abstract-rule';
8
+
9
+ export class ElementsNotAllowed extends AbstractRule {
10
+ protected selector: string = `head *${[
11
+ ':not(base)',
12
+ ':not(link)',
13
+ ':not(meta)',
14
+ ':not(script)',
15
+ ':not(style)',
16
+ ':not(title)',
17
+ ':not(noscript)',
18
+ ':not(template)'
19
+ ].join('')}`;
20
+
21
+ protected ruleConfig: IAbstractRuleConfig = {
22
+ id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.elements_not_allowed_in_head),
23
+ links: [],
24
+ recommendations: [],
25
+ severity: $severity.low,
26
+ type: CATEGORY_TYPE.BEST_PRACTICE
27
+ };
28
+
29
+ public validate(elements: Element[]): void {
30
+ const reportNode = (element: Element): void => {
31
+ const reportMessage: string = TranslateService.instant('elements_not_allowed_in_head_report_message', [
32
+ DomUtility.getEscapedOuterHTML(element),
33
+ TextUtility.escape('<head>')
34
+ ]);
35
+
36
+ const report: IIssueReport = {
37
+ message: reportMessage,
38
+ node: element,
39
+ ruleId: this.ruleConfig.id
40
+ };
41
+
42
+ this.validator.report(report);
43
+ };
44
+
45
+ elements.forEach(reportNode);
46
+ }
47
+ }
@@ -0,0 +1,55 @@
1
+ # empty-title-attribute
2
+
3
+ ## Rule id
4
+
5
+ `empty-title-attribute`
6
+
7
+ ## Definition
8
+
9
+ This rule verifies if there are elements that have an attribute `title` with an empty value. This includes also whitespaces.
10
+
11
+ ## Purpose
12
+
13
+ `title` attribute should not be empty. This will ensure that the value of `title` will be processed in a predictable way. As a side effect removing an empty value of `title` attribute will make the code smaller, which means less data to transfer and faster processing by e.g. the browser.
14
+
15
+ Following elements are being skip while evaluating this rule:
16
+
17
+ <img/>,
18
+ <html>,
19
+ <head>,
20
+ <title>,
21
+ <body>,
22
+ <link/>,
23
+ <meta>,
24
+ <title>,
25
+ <style>,
26
+ <script>,
27
+ <noscript>,
28
+ <iframe>,
29
+ <br/>,
30
+ <hr/>
31
+
32
+ ## Test cases
33
+
34
+ ### Passed
35
+
36
+ The rule passes when element have an attribute `title` with non-empty value. This includes also whitespaces.
37
+
38
+ ## WCAG Success Criteria
39
+
40
+ Not Applicable
41
+
42
+ ## Best Practice
43
+
44
+ Yes
45
+
46
+ ## User Impact
47
+
48
+ * **Severity**: low
49
+ * **Disabilities Affected**:
50
+ * Visual:
51
+ * blindness
52
+
53
+ ## Resources
54
+
55
+ * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/title#accessibility_concerns