@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.
- package/.editorconfig +13 -0
- package/.eslintrc +78 -0
- package/.gitattributes +5 -0
- package/.nvmrc +1 -0
- package/CLAUDE.md +33 -0
- package/README.md +72 -0
- package/docs/arrayUtils.js.html +69 -0
- package/docs/data/search.json +1 -0
- package/docs/domUtils.js.html +182 -0
- package/docs/fonts/Inconsolata-Regular.ttf +0 -0
- package/docs/fonts/OpenSans-Regular.ttf +0 -0
- package/docs/fonts/WorkSans-Bold.ttf +0 -0
- package/docs/getAccessibleName.js.html +456 -0
- package/docs/getAccessibleText.js.html +65 -0
- package/docs/getAriaAttributesByElement.js.html +22 -0
- package/docs/getCSSGeneratedContent.js.html +62 -0
- package/docs/getComputedRole.js.html +172 -0
- package/docs/getFocusableElements.js.html +29 -0
- package/docs/getGeneratedContent.js.html +18 -0
- package/docs/getImageText.js.html +28 -0
- package/docs/getStyleObject.js.html +48 -0
- package/docs/global.html +3 -0
- package/docs/hasAccessibleName.js.html +30 -0
- package/docs/hasAttribute.js.html +18 -0
- package/docs/hasCSSGeneratedContent.js.html +23 -0
- package/docs/hasHiddenParent.js.html +32 -0
- package/docs/hasParent.js.html +57 -0
- package/docs/hasValidAriaAttributes.js.html +33 -0
- package/docs/hasValidAriaRole.js.html +32 -0
- package/docs/index.html +3 -0
- package/docs/index.js.html +66 -0
- package/docs/isAriaAttributesValid.js.html +76 -0
- package/docs/isComplexTable.js.html +112 -0
- package/docs/isDataTable.js.html +241 -0
- package/docs/isFocusable.js.html +37 -0
- package/docs/isHidden.js.html +20 -0
- package/docs/isOffScreen.js.html +19 -0
- package/docs/isValidUrl.js.html +16 -0
- package/docs/isVisible.js.html +65 -0
- package/docs/module-afixt-test-utils.html +3 -0
- package/docs/scripts/core.js +726 -0
- package/docs/scripts/core.min.js +23 -0
- package/docs/scripts/resize.js +90 -0
- package/docs/scripts/search.js +265 -0
- package/docs/scripts/search.min.js +6 -0
- package/docs/scripts/third-party/Apache-License-2.0.txt +202 -0
- package/docs/scripts/third-party/fuse.js +9 -0
- package/docs/scripts/third-party/hljs-line-num-original.js +369 -0
- package/docs/scripts/third-party/hljs-line-num.js +1 -0
- package/docs/scripts/third-party/hljs-original.js +5171 -0
- package/docs/scripts/third-party/hljs.js +1 -0
- package/docs/scripts/third-party/popper.js +5 -0
- package/docs/scripts/third-party/tippy.js +1 -0
- package/docs/scripts/third-party/tocbot.js +672 -0
- package/docs/scripts/third-party/tocbot.min.js +1 -0
- package/docs/styles/clean-jsdoc-theme-base.css +1159 -0
- package/docs/styles/clean-jsdoc-theme-dark.css +412 -0
- package/docs/styles/clean-jsdoc-theme-light.css +482 -0
- package/docs/styles/clean-jsdoc-theme-scrollbar.css +30 -0
- package/docs/styles/clean-jsdoc-theme-without-scrollbar.min.css +1 -0
- package/docs/styles/clean-jsdoc-theme.min.css +1 -0
- package/docs/testContrast.js.html +236 -0
- package/docs/testLang.js.html +578 -0
- package/docs/testOrder.js.html +93 -0
- package/jsdoc.json +67 -0
- package/package.json +32 -0
- package/src/arrayUtils.js +67 -0
- package/src/domUtils.js +179 -0
- package/src/getAccessibleName.js +454 -0
- package/src/getAccessibleText.js +63 -0
- package/src/getAriaAttributesByElement.js +19 -0
- package/src/getCSSGeneratedContent.js +60 -0
- package/src/getComputedRole.js +169 -0
- package/src/getFocusableElements.js +26 -0
- package/src/getGeneratedContent.js +15 -0
- package/src/getImageText.js +25 -0
- package/src/getStyleObject.js +45 -0
- package/src/hasAccessibleName.js +28 -0
- package/src/hasAttribute.js +15 -0
- package/src/hasCSSGeneratedContent.js +20 -0
- package/src/hasHiddenParent.js +29 -0
- package/src/hasParent.js +54 -0
- package/src/hasValidAriaAttributes.js +30 -0
- package/src/hasValidAriaRole.js +29 -0
- package/src/index.js +64 -0
- package/src/interactiveRoles.js +20 -0
- package/src/isAriaAttributesValid.js +74 -0
- package/src/isComplexTable.js +109 -0
- package/src/isDataTable.js +239 -0
- package/src/isFocusable.js +34 -0
- package/src/isHidden.js +17 -0
- package/src/isOffScreen.js +16 -0
- package/src/isValidUrl.js +13 -0
- package/src/isVisible.js +62 -0
- package/src/stringUtils.js +150 -0
- package/src/testContrast.js +233 -0
- package/src/testLang.js +575 -0
- package/src/testOrder.js +90 -0
- package/test/_template.test.js +21 -0
- package/test/arrayUtils.test.js +84 -0
- package/test/domUtils.test.js +147 -0
- package/test/generate-test-stubs.js +37 -0
- package/test/getAccessibleName.test.js +113 -0
- package/test/getAccessibleText.test.js +94 -0
- package/test/getAriaAttributesByElement.test.js +112 -0
- package/test/getCSSGeneratedContent.test.js +102 -0
- package/test/getComputedRole.test.js +180 -0
- package/test/getFocusableElements.test.js +134 -0
- package/test/getGeneratedContent.test.js +321 -0
- package/test/getImageText.test.js +21 -0
- package/test/getStyleObject.test.js +134 -0
- package/test/hasAccessibleName.test.js +59 -0
- package/test/hasAttribute.test.js +132 -0
- package/test/hasCSSGeneratedContent.test.js +143 -0
- package/test/hasHiddenParent.test.js +176 -0
- package/test/hasParent.test.js +266 -0
- package/test/hasValidAriaAttributes.test.js +79 -0
- package/test/hasValidAriaRole.test.js +98 -0
- package/test/isAriaAttributesValid.test.js +83 -0
- package/test/isComplexTable.test.js +363 -0
- package/test/isDataTable.test.js +948 -0
- package/test/isFocusable.test.js +182 -0
- package/test/isHidden.test.js +157 -0
- package/test/isOffScreen.test.js +249 -0
- package/test/isValidUrl.test.js +63 -0
- package/test/isVisible.test.js +104 -0
- package/test/setup.js +11 -0
- package/test/stringUtils.test.js +106 -0
- package/test/testContrast.test.js +77 -0
- package/test/testLang.test.js +21 -0
- package/test/testOrder.test.js +157 -0
- 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;
|
package/src/hasParent.js
ADDED
|
@@ -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
|
+
}
|