@afixt/test-utils 1.0.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 (132) hide show
  1. package/.editorconfig +13 -0
  2. package/.eslintrc +78 -0
  3. package/.gitattributes +5 -0
  4. package/.nvmrc +1 -0
  5. package/CLAUDE.md +33 -0
  6. package/README.md +72 -0
  7. package/docs/arrayUtils.js.html +69 -0
  8. package/docs/data/search.json +1 -0
  9. package/docs/domUtils.js.html +182 -0
  10. package/docs/fonts/Inconsolata-Regular.ttf +0 -0
  11. package/docs/fonts/OpenSans-Regular.ttf +0 -0
  12. package/docs/fonts/WorkSans-Bold.ttf +0 -0
  13. package/docs/getAccessibleName.js.html +456 -0
  14. package/docs/getAccessibleText.js.html +65 -0
  15. package/docs/getAriaAttributesByElement.js.html +22 -0
  16. package/docs/getCSSGeneratedContent.js.html +62 -0
  17. package/docs/getComputedRole.js.html +172 -0
  18. package/docs/getFocusableElements.js.html +29 -0
  19. package/docs/getGeneratedContent.js.html +18 -0
  20. package/docs/getImageText.js.html +28 -0
  21. package/docs/getStyleObject.js.html +48 -0
  22. package/docs/global.html +3 -0
  23. package/docs/hasAccessibleName.js.html +30 -0
  24. package/docs/hasAttribute.js.html +18 -0
  25. package/docs/hasCSSGeneratedContent.js.html +23 -0
  26. package/docs/hasHiddenParent.js.html +32 -0
  27. package/docs/hasParent.js.html +57 -0
  28. package/docs/hasValidAriaAttributes.js.html +33 -0
  29. package/docs/hasValidAriaRole.js.html +32 -0
  30. package/docs/index.html +3 -0
  31. package/docs/index.js.html +66 -0
  32. package/docs/isAriaAttributesValid.js.html +76 -0
  33. package/docs/isComplexTable.js.html +112 -0
  34. package/docs/isDataTable.js.html +241 -0
  35. package/docs/isFocusable.js.html +37 -0
  36. package/docs/isHidden.js.html +20 -0
  37. package/docs/isOffScreen.js.html +19 -0
  38. package/docs/isValidUrl.js.html +16 -0
  39. package/docs/isVisible.js.html +65 -0
  40. package/docs/module-afixt-test-utils.html +3 -0
  41. package/docs/scripts/core.js +726 -0
  42. package/docs/scripts/core.min.js +23 -0
  43. package/docs/scripts/resize.js +90 -0
  44. package/docs/scripts/search.js +265 -0
  45. package/docs/scripts/search.min.js +6 -0
  46. package/docs/scripts/third-party/Apache-License-2.0.txt +202 -0
  47. package/docs/scripts/third-party/fuse.js +9 -0
  48. package/docs/scripts/third-party/hljs-line-num-original.js +369 -0
  49. package/docs/scripts/third-party/hljs-line-num.js +1 -0
  50. package/docs/scripts/third-party/hljs-original.js +5171 -0
  51. package/docs/scripts/third-party/hljs.js +1 -0
  52. package/docs/scripts/third-party/popper.js +5 -0
  53. package/docs/scripts/third-party/tippy.js +1 -0
  54. package/docs/scripts/third-party/tocbot.js +672 -0
  55. package/docs/scripts/third-party/tocbot.min.js +1 -0
  56. package/docs/styles/clean-jsdoc-theme-base.css +1159 -0
  57. package/docs/styles/clean-jsdoc-theme-dark.css +412 -0
  58. package/docs/styles/clean-jsdoc-theme-light.css +482 -0
  59. package/docs/styles/clean-jsdoc-theme-scrollbar.css +30 -0
  60. package/docs/styles/clean-jsdoc-theme-without-scrollbar.min.css +1 -0
  61. package/docs/styles/clean-jsdoc-theme.min.css +1 -0
  62. package/docs/testContrast.js.html +236 -0
  63. package/docs/testLang.js.html +578 -0
  64. package/docs/testOrder.js.html +93 -0
  65. package/jsdoc.json +67 -0
  66. package/package.json +32 -0
  67. package/src/arrayUtils.js +67 -0
  68. package/src/domUtils.js +179 -0
  69. package/src/getAccessibleName.js +454 -0
  70. package/src/getAccessibleText.js +63 -0
  71. package/src/getAriaAttributesByElement.js +19 -0
  72. package/src/getCSSGeneratedContent.js +60 -0
  73. package/src/getComputedRole.js +169 -0
  74. package/src/getFocusableElements.js +26 -0
  75. package/src/getGeneratedContent.js +15 -0
  76. package/src/getImageText.js +25 -0
  77. package/src/getStyleObject.js +45 -0
  78. package/src/hasAccessibleName.js +28 -0
  79. package/src/hasAttribute.js +15 -0
  80. package/src/hasCSSGeneratedContent.js +20 -0
  81. package/src/hasHiddenParent.js +29 -0
  82. package/src/hasParent.js +54 -0
  83. package/src/hasValidAriaAttributes.js +30 -0
  84. package/src/hasValidAriaRole.js +29 -0
  85. package/src/index.js +64 -0
  86. package/src/interactiveRoles.js +20 -0
  87. package/src/isAriaAttributesValid.js +74 -0
  88. package/src/isComplexTable.js +109 -0
  89. package/src/isDataTable.js +239 -0
  90. package/src/isFocusable.js +34 -0
  91. package/src/isHidden.js +17 -0
  92. package/src/isOffScreen.js +16 -0
  93. package/src/isValidUrl.js +13 -0
  94. package/src/isVisible.js +62 -0
  95. package/src/stringUtils.js +150 -0
  96. package/src/testContrast.js +233 -0
  97. package/src/testLang.js +575 -0
  98. package/src/testOrder.js +90 -0
  99. package/test/_template.test.js +21 -0
  100. package/test/arrayUtils.test.js +84 -0
  101. package/test/domUtils.test.js +147 -0
  102. package/test/generate-test-stubs.js +37 -0
  103. package/test/getAccessibleName.test.js +113 -0
  104. package/test/getAccessibleText.test.js +94 -0
  105. package/test/getAriaAttributesByElement.test.js +112 -0
  106. package/test/getCSSGeneratedContent.test.js +102 -0
  107. package/test/getComputedRole.test.js +180 -0
  108. package/test/getFocusableElements.test.js +134 -0
  109. package/test/getGeneratedContent.test.js +321 -0
  110. package/test/getImageText.test.js +21 -0
  111. package/test/getStyleObject.test.js +134 -0
  112. package/test/hasAccessibleName.test.js +59 -0
  113. package/test/hasAttribute.test.js +132 -0
  114. package/test/hasCSSGeneratedContent.test.js +143 -0
  115. package/test/hasHiddenParent.test.js +176 -0
  116. package/test/hasParent.test.js +266 -0
  117. package/test/hasValidAriaAttributes.test.js +79 -0
  118. package/test/hasValidAriaRole.test.js +98 -0
  119. package/test/isAriaAttributesValid.test.js +83 -0
  120. package/test/isComplexTable.test.js +363 -0
  121. package/test/isDataTable.test.js +948 -0
  122. package/test/isFocusable.test.js +182 -0
  123. package/test/isHidden.test.js +157 -0
  124. package/test/isOffScreen.test.js +249 -0
  125. package/test/isValidUrl.test.js +63 -0
  126. package/test/isVisible.test.js +104 -0
  127. package/test/setup.js +11 -0
  128. package/test/stringUtils.test.js +106 -0
  129. package/test/testContrast.test.js +77 -0
  130. package/test/testLang.test.js +21 -0
  131. package/test/testOrder.test.js +157 -0
  132. package/vitest.config.js +25 -0
@@ -0,0 +1,169 @@
1
+ export const roleMapping = {
2
+ 'a': {
3
+ '[href]': 'link',
4
+ ':not([href])': 'text'
5
+ },
6
+ 'abbr': 'text',
7
+ 'address': 'text',
8
+ 'area': {
9
+ '[href]': 'link',
10
+ ':not([href])': 'text'
11
+ },
12
+ 'article': 'article',
13
+ 'aside': 'complementary',
14
+ 'audio': 'group',
15
+ 'b': 'text',
16
+ 'base': 'noRole',
17
+ 'bdi': 'noRole',
18
+ 'bdo': 'text',
19
+ 'blockquote': 'text',
20
+ 'body': 'document',
21
+ 'br': 'noRole',
22
+ 'button': 'button',
23
+ 'canvas': 'image',
24
+ 'caption': 'text',
25
+ 'cite': 'text',
26
+ 'code': 'text',
27
+ 'col': 'noRole',
28
+ 'colgroup': 'group',
29
+ 'data': 'noRole',
30
+ 'datalist': 'listbox',
31
+ 'dd': 'definition',
32
+ 'del': 'text',
33
+ 'details': 'group',
34
+ 'dfn': 'term',
35
+ 'dialog': 'dialog',
36
+ 'div': 'group',
37
+ 'dl': 'list',
38
+ 'dt': 'term',
39
+ 'em': 'text',
40
+ 'embed': 'noRole',
41
+ 'fieldset': 'group',
42
+ 'figcaption': 'text',
43
+ 'figure': 'figure',
44
+ 'footer': 'contentinfo',
45
+ 'form': 'form',
46
+ 'h1': 'heading',
47
+ 'h2': 'heading',
48
+ 'h3': 'heading',
49
+ 'h4': 'heading',
50
+ 'h5': 'heading',
51
+ 'h6': 'heading',
52
+ 'head': 'noRole',
53
+ 'header': 'banner',
54
+ 'hr': 'separator',
55
+ 'html': 'noRole',
56
+ 'i': 'text',
57
+ 'iframe': 'noRole',
58
+ 'img': {
59
+ '[alt=""]': 'presentation',
60
+ 'img': 'image'
61
+ },
62
+ 'input': {
63
+ '[type="button"]': 'button',
64
+ '[type="checkbox"]': 'checkbox',
65
+ '[type="hidden"]': 'noRole',
66
+ '[type="image"]': 'button',
67
+ '[type="number"]': 'spinbutton',
68
+ '[type="radio"]': 'radio',
69
+ '[type="range"]': 'slider',
70
+ '[type="reset"]': 'button',
71
+ '[type="submit"]': 'button',
72
+ '[type="password"]': 'textbox',
73
+ '[type="color"]': 'textbox',
74
+ '[type="search"]': 'searchbox',
75
+ '[type="tel"]': 'textbox',
76
+ '[type="text"]': 'textbox',
77
+ '[type="url"]': 'textbox'
78
+ },
79
+ 'ins': 'text',
80
+ 'kbd': 'text',
81
+ 'label': 'text',
82
+ 'legend': 'text',
83
+ 'li': 'listitem',
84
+ 'link': 'noRole',
85
+ 'main': 'main',
86
+ 'map': 'noRole',
87
+ 'mark': 'text',
88
+ 'math': 'math',
89
+ 'menu': 'menu',
90
+ 'meta': 'noRole',
91
+ 'meter': 'text',
92
+ 'nav': 'navigation',
93
+ 'noscript': 'noRole',
94
+ 'object': 'noRole',
95
+ 'ol': 'list',
96
+ 'optgroup': 'group',
97
+ 'option': 'option',
98
+ 'output': 'status',
99
+ 'p': 'text',
100
+ 'param': 'noRole',
101
+ 'picture': 'noRole',
102
+ 'pre': 'text',
103
+ 'progress': 'progressbar',
104
+ 'q': 'text',
105
+ 'rb': 'noRole',
106
+ 'rp': 'noRole',
107
+ 'rt': 'text',
108
+ 'rtc': 'noRole',
109
+ 'ruby': 'text',
110
+ 's': 'text',
111
+ 'samp': 'text',
112
+ 'script': 'noRole',
113
+ 'section': 'region',
114
+ 'select': 'combobox',
115
+ 'small': 'text',
116
+ 'source': 'noRole',
117
+ 'span': 'group',
118
+ 'strong': 'text',
119
+ 'style': 'noRole',
120
+ 'sub': 'text',
121
+ 'summary': 'button',
122
+ 'sup': 'text',
123
+ 'svg': 'noRole',
124
+ 'table': 'table',
125
+ 'tbody': 'rowgroup',
126
+ 'td': 'cell',
127
+ 'template': 'noRole',
128
+ 'textarea': 'textbox',
129
+ 'tfoot': 'rowgroup',
130
+ 'th': 'columnheader',
131
+ 'thead': 'rowgroup',
132
+ 'time': 'text',
133
+ 'title': 'noRole',
134
+ 'tr': 'row',
135
+ 'track': 'noRole',
136
+ 'u': 'text',
137
+ 'ul': 'list',
138
+ 'var': 'text',
139
+ 'video': 'group',
140
+ 'wbr': 'noRole'
141
+ };
142
+
143
+ /**
144
+ * Gets the computed role of an HTML element.
145
+ *
146
+ * @param {HTMLElement} element - The HTML element to get the role for.
147
+ * @returns {string|undefined} The computed role of the element, or undefined if no role is found.
148
+ */
149
+ export function getComputedRole(element) {
150
+ if (!element) return undefined;
151
+
152
+ const roleAttr = element.getAttribute('role');
153
+ if (roleAttr) return roleAttr;
154
+
155
+ const tagName = element.tagName.toLowerCase();
156
+ const role = roleMapping[tagName];
157
+
158
+ if (typeof role === 'string') {
159
+ return role;
160
+ } else if (typeof role === 'object') {
161
+ for (const key in role) {
162
+ if (element.matches(key)) {
163
+ return role[key];
164
+ }
165
+ }
166
+ }
167
+
168
+ return undefined;
169
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ *
3
+ * @param {Element} el - the element to be tested
4
+ * @returns {Array} - Array of focusable elements
5
+ */
6
+ export function getFocusableElements(el) {
7
+ const focusableSelectors = [
8
+ "a[href]",
9
+ "area",
10
+ "button",
11
+ "select",
12
+ "textarea",
13
+ 'input:not([type="hidden"])',
14
+ "[tabindex]",
15
+ ];
16
+
17
+ return Array.from(
18
+ el.querySelectorAll(focusableSelectors.join(", "))
19
+ ).filter((element) => {
20
+ const tabindex = element.getAttribute("tabindex");
21
+ return (
22
+ (tabindex === null || parseInt(tabindex, 10) >= 0) &&
23
+ element.offsetParent !== null // Checks visibility
24
+ );
25
+ });
26
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Get the generated content for an element (`::before`, `::after`, and inner content).
3
+ * @param {Element} el - The DOM element.
4
+ * @returns {string|boolean} The generated content or `false` if not available.
5
+ */
6
+ export function getGeneratedContent(el) {
7
+ if (!el) return false;
8
+ const computedStyle = getComputedStyle(el);
9
+ const before = computedStyle.getPropertyValue("content", "::before") || "";
10
+ const inner = el.textContent || "";
11
+ const after = computedStyle.getPropertyValue("content", "::after") || "";
12
+ return before || inner || after
13
+ ? `${before} ${inner} ${after}`.trim()
14
+ : false;
15
+ }
@@ -0,0 +1,25 @@
1
+ import Tesseract from 'tesseract.js';
2
+
3
+ /**
4
+ * Extracts text from an image using OCR.
5
+ * @param {string} imagePath - The path or URL of the image.
6
+ * @returns {Promise<string|false>} Extracted text if found, otherwise false.
7
+ */
8
+ export async function getImageText(imagePath) {
9
+ try {
10
+ const { data: { text } } = await Tesseract.recognize(
11
+ imagePath,
12
+ 'eng', // Language (English)
13
+ { logger: m => console.log(m) } // Optional logging
14
+ );
15
+
16
+ const extractedText = text.trim();
17
+ return extractedText.length > 0 ? extractedText : false;
18
+ } catch (error) {
19
+ console.error('Error processing image:', error);
20
+ return false;
21
+ }
22
+ }
23
+
24
+ // Example usage:
25
+ // getImageText('path/to/image.jpg').then(result => console.log(result));
@@ -0,0 +1,45 @@
1
+
2
+ /**
3
+ * Retrieves the computed style properties of a given DOM element and returns them as an object.
4
+ *
5
+ * @param {Element} el - The DOM element for which to retrieve the computed style properties.
6
+ * @returns {Object|boolean} An object containing the computed style properties of the element,
7
+ * with property names in camelCase. Returns false if the style object
8
+ * cannot be retrieved.
9
+ */
10
+ export function getStyleObject(el) {
11
+ // Ensure we have a valid DOM element
12
+ if (!el || !(el instanceof Element)) {
13
+ return false;
14
+ }
15
+
16
+ let style;
17
+ const returns = {};
18
+ let prop;
19
+
20
+ if (window.getComputedStyle) {
21
+ const camelize = function (a, b) {
22
+ return b.toUpperCase();
23
+ };
24
+
25
+ style = window.getComputedStyle(el, null);
26
+
27
+ // Bail out if we don't get a style object
28
+ if (!style || style.length === 0) {
29
+ return false;
30
+ }
31
+ else {
32
+ for (let i = 0, l = style.length; i < l; i++) {
33
+ prop = style[i];
34
+ let camel = prop.replace(/-([a-z])/g, camelize);
35
+ let val = style.getPropertyValue(prop);
36
+ returns[camel] = val;
37
+ }
38
+ }
39
+
40
+ return returns;
41
+ }
42
+ else {
43
+ return false;
44
+ }
45
+ }
@@ -0,0 +1,28 @@
1
+ import getAccessibleName from "./getAccessibleName.js";
2
+
3
+ /**
4
+ * Determines if an element has an accessible name
5
+ * @param {Element} element - The DOM element to check
6
+ * @returns {boolean} True if the element has an accessible name, false otherwise
7
+ */
8
+ function hasAccessibleName(element) {
9
+ // If element is invalid, return false
10
+ if (!element) return false;
11
+
12
+ // Get the accessible name
13
+ const accessibleName = getAccessibleName(element);
14
+
15
+ // Return true if there is a valid accessible name, false otherwise
16
+ return accessibleName !== false;
17
+ }
18
+
19
+ // Export for ES modules
20
+ export default hasAccessibleName;
21
+
22
+ // Export for CommonJS
23
+ if (typeof module !== 'undefined' && module.exports) {
24
+ module.exports = hasAccessibleName;
25
+ } else if (typeof window !== 'undefined') {
26
+ // Add to window object for browser usage
27
+ window.hasAccessibleName = hasAccessibleName;
28
+ }
@@ -0,0 +1,15 @@
1
+
2
+ /**
3
+ * Checks if a given element has a specified attribute.
4
+ *
5
+ * @param {string} attribute - The name of the attribute to check for.
6
+ * @param {Element} element - The DOM element to check the attribute on.
7
+ * @returns {boolean} - Returns true if the attribute exists on the element, otherwise false.
8
+ */
9
+ export function hasAttribute(attribute, element) {
10
+ if (!element || !(element instanceof Element) || typeof attribute !== 'string') {
11
+ return false;
12
+ }
13
+
14
+ return element.hasAttribute(attribute);
15
+ };
@@ -0,0 +1,20 @@
1
+
2
+ import { getGeneratedContent } from './getGeneratedContent.js';
3
+
4
+ /**
5
+ * Checks if an element has CSS generated content in its ::before or ::after pseudo-elements
6
+ * or text content.
7
+ *
8
+ * @param {HTMLElement} el - The element to check for generated content.
9
+ * @returns {boolean} True if the element has any generated content, otherwise false.
10
+ */
11
+ export function hasCSSGeneratedContent(el) {
12
+ if (!el) return false;
13
+
14
+ // Use getGeneratedContent and convert its result to a boolean
15
+ const content = getGeneratedContent(el);
16
+
17
+ // getGeneratedContent returns either a string or false
18
+ // We want to return true if content exists, false otherwise
19
+ return content !== false;
20
+ }
@@ -0,0 +1,29 @@
1
+
2
+ import isHidden from './isHidden.js';
3
+
4
+ /**
5
+ * Checks if the given element has a hidden parent element.
6
+ *
7
+ * @param {HTMLElement} element - The DOM element to check.
8
+ * @returns {boolean} - Returns true if the element has a hidden parent, otherwise false.
9
+ */
10
+ const hasHiddenParent = (element) => {
11
+ if (!element || !(element instanceof Element)) {
12
+ return false;
13
+ }
14
+
15
+ let visible = true;
16
+ let parent = element.parentElement;
17
+
18
+ while (parent) {
19
+ if (isHidden(parent)) {
20
+ visible = false;
21
+ break;
22
+ }
23
+ parent = parent.parentElement;
24
+ }
25
+
26
+ return !visible;
27
+ };
28
+
29
+ export default hasHiddenParent;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Checks if the given element has a parent that matches any of the selectors in the array.
3
+ *
4
+ * @param {HTMLElement} element - The DOM element to check.
5
+ * @param {string[]} selectors - An array of selectors to match against the element's parents.
6
+ * @returns {boolean} - Returns true if the element has a parent that matches any of the selectors, otherwise false.
7
+ */
8
+ export function hasParent(element, selectors) {
9
+ // Input validation
10
+ if (!element || !(element instanceof Element)) {
11
+ return false;
12
+ }
13
+
14
+ if (!selectors || !Array.isArray(selectors) || selectors.length === 0) {
15
+ return false;
16
+ }
17
+
18
+ // Get all parents
19
+ let currentParent = element.parentElement;
20
+
21
+ // Check each parent against all selectors
22
+ while (currentParent) {
23
+ // Check if the current parent matches any of the selectors
24
+ for (const selector of selectors) {
25
+ try {
26
+ if (!selector || typeof selector !== 'string') {
27
+ continue;
28
+ }
29
+
30
+ // Handle special case for tag name matching
31
+ if (selector.indexOf('#') === -1 &&
32
+ selector.indexOf('.') === -1 &&
33
+ selector.indexOf('[') === -1) {
34
+ // Simple tag name comparison
35
+ if (currentParent.tagName.toLowerCase() === selector.toLowerCase()) {
36
+ return true;
37
+ }
38
+ }
39
+ // Use matches for CSS selector matching
40
+ else if (currentParent.matches(selector)) {
41
+ return true;
42
+ }
43
+ } catch (e) {
44
+ // Invalid selector, skip this one and warn
45
+ console.warn(`Invalid selector: ${selector}`);
46
+ }
47
+ }
48
+
49
+ // Move up to the next parent
50
+ currentParent = currentParent.parentElement;
51
+ }
52
+
53
+ return false;
54
+ };
@@ -0,0 +1,30 @@
1
+ import { isAriaAttributeValid } from './isAriaAttributesValid.js';
2
+
3
+ /**
4
+ * Checks if an element has any valid ARIA attributes.
5
+ *
6
+ * @param {Element} element - The DOM element to check
7
+ * @returns {boolean} True if the element has at least one valid ARIA attribute, false otherwise
8
+ */
9
+ export function hasValidAriaAttributes(element) {
10
+ if (!element || typeof element.hasAttributes !== 'function' || !element.attributes) {
11
+ return false;
12
+ }
13
+
14
+ // Check if the element has any attributes
15
+ if (!element.hasAttributes()) {
16
+ return false;
17
+ }
18
+
19
+ // Iterate through all attributes and check if any are valid ARIA attributes
20
+ for (const attribute of element.attributes) {
21
+ const attributeName = attribute.name.toLowerCase();
22
+
23
+ // Check if this is an aria attribute (starts with 'aria-')
24
+ if (attributeName.startsWith('aria-') && isAriaAttributeValid(attributeName)) {
25
+ return true;
26
+ }
27
+ }
28
+
29
+ return false;
30
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Checks if the given element has a valid ARIA role.
3
+ *
4
+ * @param {HTMLElement} element - The DOM element to check.
5
+ * @returns {boolean} True if the element has a valid ARIA role, false otherwise.
6
+ */
7
+ export function hasValidAriaRole(element) {
8
+ const validAriaRoles = new Set([
9
+ "alert", "alertdialog", "button", "checkbox", "dialog", "gridcell", "link",
10
+ "log", "marquee", "menuitem", "menuitemcheckbox", "menuitemradio", "option",
11
+ "progressbar", "radio", "scrollbar", "searchbox", "slider", "spinbutton",
12
+ "status", "switch", "tab", "tabpanel", "textbox", "tooltip", "treeitem",
13
+ "combobox", "grid", "listbox", "menu", "menubar", "radiogroup", "tablist",
14
+ "tree", "treegrid", "article", "cell", "columnheader", "definition",
15
+ "directory", "document", "feed", "figure", "group", "heading", "img",
16
+ "list", "listitem", "math", "none", "note", "presentation", "row",
17
+ "rowgroup", "rowheader", "separator", "table", "term", "toolbar",
18
+ "application", "banner", "complementary", "contentinfo", "form", "main",
19
+ "navigation", "region", "search", "timer"
20
+ ]);
21
+
22
+ if (!element || typeof element.getAttribute !== 'function') return false;
23
+
24
+ const roleAttr = element.getAttribute('role');
25
+ if (!roleAttr) return false;
26
+
27
+ const roles = roleAttr.trim().split(/\s+/);
28
+ return validAriaRoles.has(roles[0]); // Only check the first role
29
+ }
package/src/index.js ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @file Main entry point for the AFixt test utilities library
3
+ * @module afixt-test-utils
4
+ * @description A collection of utility functions for accessibility testing
5
+ */
6
+
7
+ // Array utilities
8
+ export * from './arrayUtils.js';
9
+
10
+ // DOM utilities
11
+ export * from './domUtils.js';
12
+
13
+ // Accessibility name computation
14
+ export * from './getAccessibleName.js';
15
+ export * from './getAccessibleText.js';
16
+ export * from './hasAccessibleName.js';
17
+
18
+ // ARIA utilities
19
+ export * from './getAriaAttributesByElement.js';
20
+ export * from './hasValidAriaAttributes.js';
21
+ export * from './hasValidAriaRole.js';
22
+ export * from './isAriaAttributesValid.js';
23
+ export * from './interactiveRoles.js';
24
+
25
+ // CSS utilities
26
+ export * from './getCSSGeneratedContent.js';
27
+ export * from './getGeneratedContent.js';
28
+ export * from './getStyleObject.js';
29
+ export * from './hasCSSGeneratedContent.js';
30
+
31
+ // Table utilities
32
+ export * from './isComplexTable.js';
33
+ export * from './isDataTable.js';
34
+
35
+ // Visibility and positioning
36
+ export * from './isHidden.js';
37
+ export * from './isOffScreen.js';
38
+ export * from './isVisible.js';
39
+ export * from './hasHiddenParent.js';
40
+
41
+ // Element relationships
42
+ export * from './hasParent.js';
43
+ export * from './hasAttribute.js';
44
+
45
+ // Focus management
46
+ export * from './getFocusableElements.js';
47
+ export * from './isFocusable.js';
48
+
49
+ // Role computation
50
+ export * from './getComputedRole.js';
51
+
52
+ // Image utilities
53
+ export * from './getImageText.js';
54
+
55
+ // Testing utilities
56
+ export * from './testContrast.js';
57
+ export * from './testLang.js';
58
+ export * from './testOrder.js';
59
+
60
+ // URL utilities
61
+ export * from './isValidUrl.js';
62
+
63
+ // String utilities
64
+ export * from './stringUtils.js';
@@ -0,0 +1,20 @@
1
+ const interactiveRoles = [
2
+ "button",
3
+ "checkbox",
4
+ "combobox",
5
+ "link",
6
+ "menu",
7
+ "menuitemcheckbox",
8
+ "menuitemradio",
9
+ "radio",
10
+ "scrollbar",
11
+ "slider",
12
+ "spinbutton",
13
+ "tablist",
14
+ "textbox",
15
+ "toolbar",
16
+ "switch",
17
+ "tree",
18
+ ];
19
+
20
+ export default interactiveRoles;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * List of valid ARIA attributes based on WAI-ARIA specification.
3
+ * @type {Set<string>}
4
+ */
5
+ const validAriaAttributes = new Set([
6
+ // ARIA States and Properties
7
+ 'aria-activedescendant',
8
+ 'aria-atomic',
9
+ 'aria-autocomplete',
10
+ 'aria-braillelabel',
11
+ 'aria-brailleroledescription',
12
+ 'aria-busy',
13
+ 'aria-checked',
14
+ 'aria-colcount',
15
+ 'aria-colindex',
16
+ 'aria-colindextext',
17
+ 'aria-colspan',
18
+ 'aria-controls',
19
+ 'aria-current',
20
+ 'aria-describedby',
21
+ 'aria-description',
22
+ 'aria-details',
23
+ 'aria-disabled',
24
+ 'aria-dropeffect',
25
+ 'aria-errormessage',
26
+ 'aria-expanded',
27
+ 'aria-flowto',
28
+ 'aria-grabbed',
29
+ 'aria-haspopup',
30
+ 'aria-hidden',
31
+ 'aria-invalid',
32
+ 'aria-keyshortcuts',
33
+ 'aria-label',
34
+ 'aria-labelledby',
35
+ 'aria-level',
36
+ 'aria-live',
37
+ 'aria-modal',
38
+ 'aria-multiline',
39
+ 'aria-multiselectable',
40
+ 'aria-orientation',
41
+ 'aria-owns',
42
+ 'aria-placeholder',
43
+ 'aria-posinset',
44
+ 'aria-pressed',
45
+ 'aria-readonly',
46
+ 'aria-relevant',
47
+ 'aria-required',
48
+ 'aria-roledescription',
49
+ 'aria-rowcount',
50
+ 'aria-rowindex',
51
+ 'aria-rowindextext',
52
+ 'aria-rowspan',
53
+ 'aria-selected',
54
+ 'aria-setsize',
55
+ 'aria-sort',
56
+ 'aria-valuemax',
57
+ 'aria-valuemin',
58
+ 'aria-valuenow',
59
+ 'aria-valuetext'
60
+ ]);
61
+
62
+ /**
63
+ * Checks if a specific ARIA attribute is valid according to the WAI-ARIA specification.
64
+ *
65
+ * @param {string} attributeName - The name of the ARIA attribute to check (e.g., 'aria-label')
66
+ * @returns {boolean} True if the attribute is a valid ARIA attribute, false otherwise
67
+ */
68
+ export function isAriaAttributeValid(attributeName) {
69
+ if (!attributeName || typeof attributeName !== 'string') {
70
+ return false;
71
+ }
72
+
73
+ return validAriaAttributes.has(attributeName.toLowerCase());
74
+ }