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,83 @@
1
+ import { IRule } from '../interfaces/rule.interface';
2
+ import { CATEGORY_TYPE } from '../constants/categoryType';
3
+ import { $severity } from '../constants/accessibility';
4
+ import { DomUtility } from '../utils/dom';
5
+ import { Validator } from '../validator';
6
+ import { Config } from '../config';
7
+ import { Context } from '../interfaces/context.interface';
8
+
9
+ type Nodes = Document[] | HTMLElement[] | Element[] | StyleSheetList[] | CSSStyleSheet[];
10
+
11
+ export interface IAbstractRuleConfig {
12
+ id: string;
13
+ type: CATEGORY_TYPE;
14
+ severity: $severity;
15
+ links: { content: string; url: string }[];
16
+ recommendations: any[];
17
+ }
18
+
19
+ export abstract class AbstractRule implements IRule {
20
+ protected context: Context;
21
+ protected options: any;
22
+ protected validator: typeof Validator;
23
+ protected selector: string | (() => Nodes);
24
+ protected abstract ruleConfig: IAbstractRuleConfig;
25
+
26
+ constructor() {
27
+ this.validator = Validator;
28
+ this.selector = 'html';
29
+ this.context = document.documentElement;
30
+ }
31
+
32
+ protected collectElements(): Nodes | null {
33
+ if (typeof this.selector === 'undefined') {
34
+ throw new Error(`[AbstractRule] The selector must be a string or function, but there is type of ${typeof this.selector}`);
35
+ }
36
+
37
+ if (typeof this.selector === 'function') {
38
+ return this.selector();
39
+ }
40
+
41
+ return DomUtility.querySelectorAllExclude(
42
+ this.selector,
43
+ this.context,
44
+ Config.excludeContainers,
45
+ []
46
+ );
47
+ }
48
+
49
+ public abstract validate(nodes: Document[] | HTMLElement[] | Element[] | StyleSheetList | StyleSheetList[] | CSSStyleSheet[] | null): void;
50
+
51
+ public get config(): IAbstractRuleConfig {
52
+ return this.ruleConfig;
53
+ }
54
+
55
+ public get id(): string {
56
+ return this.config.id;
57
+ }
58
+
59
+ // eslint-disable-next-line require-await
60
+ public async run(context: Context, validator?: typeof Validator, options?: any): Promise<void> {
61
+ this.context = context;
62
+ this.options = (typeof options === 'object') ? options : {};
63
+ this.validator = (typeof validator !== 'undefined') ? validator : Validator;
64
+
65
+ let nodes: Nodes | null = this.collectElements();
66
+ const totalElementsEvaluated: number = nodes === null ? 0 : nodes.length;
67
+
68
+ this.validator.setTotalElementsEvaluated(this.ruleConfig.id, totalElementsEvaluated);
69
+
70
+ const getFilter = (filter: Function): void => {
71
+ nodes = filter(this.ruleConfig.id, nodes);
72
+ };
73
+
74
+ Config.getFilters('before-rule-validate').forEach(getFilter);
75
+
76
+ this.validate(nodes);
77
+ }
78
+
79
+ public registerValidator(): void {
80
+ this.validator.register(this.ruleConfig, this.run.bind(this));
81
+ }
82
+
83
+ }
@@ -0,0 +1,73 @@
1
+ import { AriaHiddenFalse } from './aria-hidden-false';
2
+ import { DomUtility } from '../../../utils/dom';
3
+ import { Validator } from '../../../validator';
4
+
5
+ describe('Rules', () => {
6
+
7
+ describe('AriaHiddenFalse', () => {
8
+
9
+ let fakeDom;
10
+ const selector = 'body [aria-hidden="false"]';
11
+
12
+ new AriaHiddenFalse().registerValidator();
13
+
14
+ beforeEach(() => {
15
+ fakeDom = document.createElement('div');
16
+ fakeDom.id = 'fakedom';
17
+ document.body.appendChild(fakeDom);
18
+
19
+ Validator.reset();
20
+ });
21
+
22
+ afterEach(() => {
23
+ DomUtility.remove(document.getElementById('fakedom'));
24
+ fakeDom = undefined;
25
+ });
26
+
27
+ it('should return one report when there is an element with aria-hidden="false"', () => {
28
+ fakeDom.innerHTML = '<p aria-hidden="false"></p>';
29
+
30
+ const nodes: Element[] = DomUtility.querySelectorAllExclude(selector, fakeDom);
31
+
32
+ new AriaHiddenFalse().validate(nodes);
33
+
34
+ expect(Validator.getReport('report_0').ruleId).toBe('aria-hidden-false');
35
+ expect(Validator.getReport('report_0').message).toBe('You have defined <code>aria-hidden&#x3D;&quot;false&quot;</code>. Caution, as the child content is always readable by screen readers regardless of setting <code>display: none</code> on any child element.');
36
+ expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('p');
37
+ });
38
+
39
+ it('should return 1 report when there is an element with attribute aria-hidden="false" and style="display: none"', () => {
40
+ fakeDom.innerHTML = '<p aria-hidden="false" style="display: none"></p>';
41
+
42
+ const nodes = DomUtility.querySelectorAllExclude(selector, fakeDom);
43
+
44
+ new AriaHiddenFalse().validate(nodes);
45
+
46
+ expect(Object.keys(Validator.getReports()).length).toBe(1);
47
+ expect(Validator.getReport('report_0').message).toBe('You have defined <code>aria-hidden&#x3D;&quot;false&quot;</code>. Caution, as the child content is always readable by screen readers regardless of setting <code>display: none</code> on any child element.');
48
+ expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('p');
49
+ });
50
+
51
+ it('should return no report when there is an element with attribute aria-hidden="true"', () => {
52
+ fakeDom.innerHTML = '<p aria-hidden="true"></p>';
53
+
54
+ const nodes = DomUtility.querySelectorAllExclude(selector, fakeDom);
55
+
56
+ new AriaHiddenFalse().validate(nodes);
57
+
58
+ expect(Object.keys(Validator.getReports()).length).toBe(0);
59
+ });
60
+
61
+ it('should return no report when there is no element with attribute aria-hidden="false"', () => {
62
+ fakeDom.innerHTML = '<p>test</p>';
63
+
64
+ const nodes = DomUtility.querySelectorAllExclude(selector, fakeDom);
65
+
66
+ new AriaHiddenFalse().validate(nodes);
67
+
68
+ expect(Object.keys(Validator.getReports()).length).toBe(0);
69
+ });
70
+
71
+ });
72
+
73
+ });
@@ -0,0 +1,34 @@
1
+ import { CATEGORY_TYPE } from '../../../constants/categoryType';
2
+ import { IIssueReport } from '../../../interfaces/rule-issue.interface';
3
+ import { TranslateService } from '../../../services/translate';
4
+ import { $severity, $accessibilityAuditRules } from '../../../constants/accessibility';
5
+ import { TextUtility } from '../../../utils/text';
6
+ import { AbstractRule, IAbstractRuleConfig } from '../../abstract-rule';
7
+
8
+ export class AriaHiddenFalse extends AbstractRule {
9
+ protected selector: string = 'body [aria-hidden="false"]';
10
+
11
+ protected ruleConfig: IAbstractRuleConfig = {
12
+ id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.aria_hidden_false),
13
+ links: [],
14
+ recommendations: [],
15
+ severity: $severity.low,
16
+ type: CATEGORY_TYPE.BEST_PRACTICE
17
+ };
18
+
19
+ public validate(elements: Element[]): void {
20
+ const reportProblem = (element: Element): void => {
21
+ const reportMessage: string = TranslateService.instant('aria_hidden_false_report_message', [TextUtility.escape('aria-hidden="false"'), TextUtility.escape('display: none')]);
22
+
23
+ const problem: IIssueReport = {
24
+ message: reportMessage,
25
+ node: element,
26
+ ruleId: this.ruleConfig.id
27
+ };
28
+
29
+ this.validator.report(problem);
30
+ };
31
+
32
+ elements.forEach(reportProblem);
33
+ }
34
+ }
@@ -0,0 +1,32 @@
1
+ # aria-hidden
2
+
3
+ ## Rule id
4
+
5
+ `aria-hidden`
6
+
7
+ ## Definition
8
+
9
+ `aria-hidden="false"` should be used with caution.
10
+
11
+ ## Purpose
12
+
13
+ When `aria-hidden="false"` is defined then we need to be cautious, as the child content is always readable by screen readers regardless of setting `aria-hidden="false"` on any child element.
14
+
15
+ ## WCAG Success Criteria
16
+
17
+ Not Applicable
18
+
19
+ ## Best Practice
20
+
21
+ Yes
22
+
23
+ ## User Impact
24
+
25
+ * **Severity**: low
26
+ * **Disabilities Affected**:
27
+ * Visual:
28
+ * blindness
29
+
30
+ ## Resources
31
+
32
+ Not available
@@ -0,0 +1,49 @@
1
+ # aria-role-dialog
2
+
3
+ ## Rule id
4
+
5
+ `aria-role-dialog`
6
+
7
+ ## Definition
8
+
9
+ Ensures that element with attribute `role="dialog"` is having defined an accessible name.
10
+
11
+ ## Purpose
12
+
13
+ Using `role="dialog"` on the HTML element helps assistive technology identify the dialog's content as being grouped and separated from the rest of the page content. However, having only `role="dialog"` defined alone is not sufficient to make a dialog accessible. Additionally, the following needs to be done:
14
+
15
+ 1. The dialog must be properly labeled
16
+ 2. Keyboard focus must be managed correctly
17
+
18
+ `aria-role-dialog` rule test cases below describe how #1 requirement can be met.
19
+
20
+ **Note**: when possible you may consider using native [&lt;dialog&gt;](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog) box feature.
21
+
22
+ ## Test cases
23
+
24
+ ### Passed
25
+
26
+ The rule passes when one of the following case is fulfilled:
27
+
28
+ 1. Attribute `aria-labelledby` is defined AND is not empty.
29
+ 2. Attribute `aria-label` is defined AND is not empty.
30
+ 2. Attribute `title` is defined AND is not empty.
31
+
32
+ ## WCAG Success Criteria
33
+
34
+ Not Applicable
35
+
36
+ ## Best Practice
37
+
38
+ Yes
39
+
40
+ ## User Impact
41
+
42
+ * **Severity**: high
43
+ * **Disabilities Affected**:
44
+ * Visual:
45
+ * blindness
46
+
47
+ ## Resources
48
+
49
+ Not available
@@ -0,0 +1,91 @@
1
+ import { AriaRoleDialog } from './aria-role-dialog';
2
+ import { DomUtility } from '../../../utils/dom';
3
+ import { Validator } from '../../../validator';
4
+
5
+ describe('Rules', () => {
6
+
7
+ describe('AriaRoleDialog', () => {
8
+
9
+ let fakeDom;
10
+ const selector = '[role="dialog"], [role="alertdialog"]';
11
+
12
+ new AriaRoleDialog().registerValidator();
13
+
14
+ beforeEach(() => {
15
+ fakeDom = document.createElement('div');
16
+ fakeDom.id = 'fakedom';
17
+ document.body.appendChild(fakeDom);
18
+
19
+ Validator.reset();
20
+ });
21
+
22
+ afterEach(() => {
23
+ DomUtility.remove(document.getElementById('fakedom'));
24
+ fakeDom = undefined;
25
+ });
26
+
27
+ it('should return no report when there is an element with role="dialog" and aria-label', () => {
28
+ fakeDom.innerHTML = '<div role="dialog" aria-label="name"></div>';
29
+
30
+ const nodes: Element[] = DomUtility.querySelectorAllExclude(selector, fakeDom);
31
+
32
+ new AriaRoleDialog().validate(nodes);
33
+
34
+ expect(Object.keys(Validator.getReports()).length).toBe(0);
35
+ });
36
+
37
+ it('should return 1 report when there is an element with role="dialog" and aria-label=""', () => {
38
+ fakeDom.innerHTML = '<div role="dialog" aria-label=""></div>';
39
+
40
+ const nodes: Element[] = DomUtility.querySelectorAllExclude(selector, fakeDom);
41
+
42
+ new AriaRoleDialog().validate(nodes);
43
+
44
+ expect(Object.keys(Validator.getReports()).length).toBe(1);
45
+ expect(Validator.getReport('report_0').ruleId).toBe('aria-role-dialog');
46
+ expect(Validator.getReport('report_0').message).toBe('Element with <code>role&#x3D;&quot;dialog&quot;</code> attribute has no accessible name because attribute <code>aria-label</code> has no content (it\'s empty).');
47
+ expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('div');
48
+ });
49
+
50
+ it('should return 1 report when there is an element with role="dialog" and title=""', () => {
51
+ fakeDom.innerHTML = '<div role="dialog" title=""></div>';
52
+
53
+ const nodes: Element[] = DomUtility.querySelectorAllExclude(selector, fakeDom);
54
+
55
+ new AriaRoleDialog().validate(nodes);
56
+
57
+ expect(Object.keys(Validator.getReports()).length).toBe(1);
58
+ expect(Validator.getReport('report_0').ruleId).toBe('aria-role-dialog');
59
+ expect(Validator.getReport('report_0').message).toBe('Element with <code>role&#x3D;&quot;dialog&quot;</code> attribute has no accessible name because attribute <code>title</code> has no content (it\'s empty).');
60
+ expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('div');
61
+ });
62
+
63
+ it('should return 1 report when there is an element with role="dialog" without any accesible name', () => {
64
+ fakeDom.innerHTML = '<div role="dialog"></div>';
65
+
66
+ const nodes: Element[] = DomUtility.querySelectorAllExclude(selector, fakeDom);
67
+
68
+ new AriaRoleDialog().validate(nodes);
69
+
70
+ expect(Object.keys(Validator.getReports()).length).toBe(1);
71
+ expect(Validator.getReport('report_0').ruleId).toBe('aria-role-dialog');
72
+ expect(Validator.getReport('report_0').message).toBe('Element with <code>role&#x3D;&quot;dialog&quot;</code> attribute has no accessible name.');
73
+ expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('div');
74
+ });
75
+
76
+ it('should return 1 report when there is an element with role="alertdialog" without any accesible name', () => {
77
+ fakeDom.innerHTML = '<div role="alertdialog"></div>';
78
+
79
+ const nodes: Element[] = DomUtility.querySelectorAllExclude(selector, fakeDom);
80
+
81
+ new AriaRoleDialog().validate(nodes);
82
+
83
+ expect(Object.keys(Validator.getReports()).length).toBe(1);
84
+ expect(Validator.getReport('report_0').ruleId).toBe('aria-role-dialog');
85
+ expect(Validator.getReport('report_0').message).toBe('Element with <code>role&#x3D;&quot;alertdialog&quot;</code> attribute has no accessible name.');
86
+ expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('div');
87
+ });
88
+
89
+ });
90
+
91
+ });
@@ -0,0 +1,62 @@
1
+ import { CATEGORY_TYPE } from '../../../constants/categoryType';
2
+ import { IIssueReport } from '../../../interfaces/rule-issue.interface';
3
+ import { TranslateService } from '../../../services/translate';
4
+ import { $severity, $accessibilityAuditRules } from '../../../constants/accessibility';
5
+ import { TextUtility } from '../../../utils/text';
6
+ import { AbstractRule, IAbstractRuleConfig } from '../../abstract-rule';
7
+
8
+ export class AriaRoleDialog extends AbstractRule {
9
+ protected selector: string = '[role="dialog"], [role="alertdialog"]';
10
+
11
+ protected ruleConfig: IAbstractRuleConfig = {
12
+ id: TextUtility.convertUnderscoresToDashes($accessibilityAuditRules.aria_role_dialog),
13
+ links: [],
14
+ recommendations: [],
15
+ severity: $severity.high,
16
+ type: CATEGORY_TYPE.BEST_PRACTICE
17
+ };
18
+
19
+ public validate(elements: Element[]): void {
20
+ const reportProblem = (element: Element): void => {
21
+
22
+ const ariaLabelledby: string | null = element.getAttribute('aria-labelledby');
23
+ const ariaLabel: string | null = element.getAttribute('aria-label');
24
+ const titleAttr: string | null = element.getAttribute('title');
25
+ const roleAttr: string | null = element.getAttribute('role');
26
+
27
+ let reportMessage: string = '';
28
+
29
+ if (typeof ariaLabelledby === 'string') {
30
+ if (ariaLabelledby.trim().length > 0) {
31
+ return;
32
+ }
33
+
34
+ reportMessage = TranslateService.instant('aria_role_dialog_report_message_1', [TextUtility.escape(`role="${roleAttr}"`), TextUtility.escape('aria-labelledby')]);
35
+ } else if (typeof ariaLabel === 'string') {
36
+ if (ariaLabel.trim().length > 0) {
37
+ return;
38
+ }
39
+
40
+ reportMessage = TranslateService.instant('aria_role_dialog_report_message_1', [TextUtility.escape(`role="${roleAttr}"`), TextUtility.escape('aria-label')]);
41
+ } else if (typeof titleAttr === 'string') {
42
+ if (titleAttr.trim().length > 0) {
43
+ return;
44
+ }
45
+
46
+ reportMessage = TranslateService.instant('aria_role_dialog_report_message_1', [TextUtility.escape(`role="${roleAttr}"`), TextUtility.escape('title')]);
47
+ } else {
48
+ reportMessage = TranslateService.instant('aria_role_dialog_report_message_2', [TextUtility.escape(`role="${roleAttr}"`)]);
49
+ }
50
+
51
+ const problem: IIssueReport = {
52
+ message: reportMessage,
53
+ node: element,
54
+ ruleId: this.ruleConfig.id
55
+ };
56
+
57
+ this.validator.report(problem);
58
+ };
59
+
60
+ elements.forEach(reportProblem);
61
+ }
62
+ }
@@ -0,0 +1,44 @@
1
+ # capital-letters-words
2
+
3
+ ## Rule id
4
+
5
+ `capital-letters-words`
6
+
7
+ ## Definition
8
+
9
+ This rule detects words written in all capital letters, including text in an attribute `title` as well as using `text-transform` CSS.
10
+
11
+ ## Purpose
12
+
13
+ Unless you are dealing with an acronym, there should not be any content in all caps. Some screen readers will announce the capital letters separately (like an acronym) or otherwise misleadingly emphasise the capital letters.
14
+
15
+ **Example**: CONTACT US and Contact Us will be read be screen reader differently. You can use [Pronunciation checker](https://voicenotebook.com/prononce.php) to verify how these two words are being readable.
16
+
17
+ ## Test cases
18
+
19
+ ### Passed
20
+
21
+ The rule passes when all of the following cases are fulfilled:
22
+
23
+ 1. Text is not using all capital letters.
24
+ 2. There is no `text-transform` CSS rule used.
25
+ 3. Attribute `title` does not contains text written in all capital letters.
26
+
27
+ ## WCAG Success Criteria
28
+
29
+ Not Applicable
30
+
31
+ ## Best Practice
32
+
33
+ Yes
34
+
35
+ ## User Impact
36
+
37
+ * **Severity**: high
38
+ * **Disabilities Affected**:
39
+ * Visual:
40
+ * blindness
41
+
42
+ ## Resources
43
+
44
+ Not available
@@ -0,0 +1,111 @@
1
+ import { DomUtility } from '../../../utils/dom';
2
+ import { Validator } from '../../../validator';
3
+ import { CapitalLettersWords } from './capital-letters-words';
4
+
5
+ describe('Rules', () => {
6
+
7
+ describe('CapitalLettersWords', () => {
8
+
9
+ it('should indicate that class exists', () => {
10
+ expect(CapitalLettersWords).toBeDefined();
11
+ });
12
+
13
+ const selector: string = `*${[
14
+ ':root',
15
+ 'head',
16
+ 'style',
17
+ 'script',
18
+ 'meta',
19
+ 'link',
20
+ 'br',
21
+ 'hr',
22
+ 'object',
23
+ 'path',
24
+ 'g',
25
+ 'filter',
26
+ 'img',
27
+ 'input',
28
+ 'iframe',
29
+ 'code',
30
+ ':empty'
31
+ ].map((i: string): string => {
32
+ return `:not(${i})`;
33
+ }).join('')}`;
34
+
35
+ let fakeDom;
36
+
37
+ new CapitalLettersWords().registerValidator();
38
+
39
+ beforeEach(() => {
40
+ fakeDom = document.createElement('div');
41
+ fakeDom.id = 'fakedom';
42
+ document.body.appendChild(fakeDom);
43
+
44
+ Validator.reset();
45
+ });
46
+
47
+ afterEach(() => {
48
+ DomUtility.remove(document.getElementById('fakedom'));
49
+ fakeDom = undefined;
50
+ });
51
+
52
+ it('should return no report when text does not contains words in capital letters', () => {
53
+ fakeDom.innerHTML = '<p>Text contains no capital letters</p>';
54
+
55
+ const nodes = DomUtility.querySelectorAllExclude(selector, fakeDom) as HTMLParagraphElement[];
56
+
57
+ new CapitalLettersWords().validate(nodes);
58
+
59
+ expect(Object.keys(Validator.getReports()).length).toBe(0);
60
+ });
61
+
62
+ it('should return 1 report when text contains words in capital letters transformed through CSS text-transform', () => {
63
+ fakeDom.innerHTML = '<p style="text-transform: uppercase;">capital letters</p>';
64
+
65
+ const nodes = DomUtility.querySelectorAllExclude(selector, fakeDom) as HTMLParagraphElement[];
66
+
67
+ new CapitalLettersWords().validate(nodes);
68
+
69
+ expect(Object.keys(Validator.getReports()).length).toBe(1);
70
+ expect(Validator.getReport('report_0').message).toBe('Element have a text <q>CAPITAL LETTERS</q> that contains words in upper case. <strong>Note</strong>: the text is transformed using <code>(text-transform: uppercase)</code>. Unless you are dealing with an acronym, there should not be any content in all caps. Some screen readers will announce the capital letters separately (like an acronym) or otherwise misleadingly emphasise the capital letters.');
71
+ expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('p');
72
+ });
73
+
74
+ it('should return 1 report when text contains words in capital letters', () => {
75
+ fakeDom.innerHTML = '<p>Text contains CAPITAL LETTERS</p>';
76
+
77
+ const nodes = DomUtility.querySelectorAllExclude(selector, fakeDom) as HTMLParagraphElement[];
78
+
79
+ new CapitalLettersWords().validate(nodes);
80
+
81
+ expect(Object.keys(Validator.getReports()).length).toBe(1);
82
+ expect(Validator.getReport('report_0').message).toBe('Element have a text that contains words in upper case. Unless you are dealing with an acronym, there should not be any content in all caps. Some screen readers will announce the capital letters separately (like an acronym) or otherwise misleadingly emphasise the capital letters.');
83
+ expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('p');
84
+ });
85
+
86
+ it('should return 1 report when element have an attribute title with text that contains words in capital letters', () => {
87
+ fakeDom.innerHTML = '<p title="CAPITAL LETTERS">Example text</p>';
88
+
89
+ const nodes = DomUtility.querySelectorAllExclude(selector, fakeDom) as HTMLParagraphElement[];
90
+
91
+ new CapitalLettersWords().validate(nodes);
92
+
93
+ expect(Object.keys(Validator.getReports()).length).toBe(1);
94
+ expect(Validator.getReport('report_0').message).toBe('This element has a <code>title</code> attribute that contains words in upper case. Unless you are dealing with an acronym, there should not be any content in all caps. Some screen readers will announce the capital letters separately (like an acronym) or otherwise misleadingly emphasise the capital letters.');
95
+ expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('p');
96
+ });
97
+
98
+ it('should return 1 report when element have an attribute title and content with text that contains words in capital letters', () => {
99
+ fakeDom.innerHTML = '<p title="CAPITAL LETTERS">Example text WITH CAPITAL LETTERS</p>';
100
+
101
+ const nodes = DomUtility.querySelectorAllExclude(selector, fakeDom) as HTMLParagraphElement[];
102
+
103
+ new CapitalLettersWords().validate(nodes);
104
+
105
+ expect(Object.keys(Validator.getReports()).length).toBe(1);
106
+ expect(Validator.getReport('report_0').message).toBe('Element have a text and has a <code>title</code> attribute that contains words in upper case. Unless you are dealing with an acronym, there should not be any content in all caps. Some screen readers will announce the capital letters separately (like an acronym) or otherwise misleadingly emphasise the capital letters.');
107
+ expect(Validator.getReport('report_0').node.nodeName.toLowerCase()).toBe('p');
108
+ });
109
+
110
+ });
111
+ });