@afixt/test-utils 2.4.0 → 2.6.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/package.json +1 -1
- package/src/constants.js +46 -0
- package/src/detectFocusTrap.js +484 -0
- package/src/getAccessibleText.js +32 -2
- package/src/index.js +2 -0
- package/src/shadowDomUtils.js +83 -0
- package/src/stringUtils.js +10 -1
- package/test/detectFocusTrap.test.js +1004 -0
- package/test/getAccessibleText.test.js +74 -0
- package/test/hasValidAriaRole.test.js +270 -151
- package/test/index.test.js +3 -0
- package/test/shadowDomUtils.test.js +98 -0
package/src/shadowDomUtils.js
CHANGED
|
@@ -111,8 +111,91 @@ function deepQuerySelectorAll(root, selector) {
|
|
|
111
111
|
return results;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Recursively extracts text content from an element, crossing open shadow
|
|
116
|
+
* DOM boundaries. For closed shadow roots the host element's light-DOM
|
|
117
|
+
* `textContent` is used (the shadow internals are inaccessible by spec).
|
|
118
|
+
*
|
|
119
|
+
* @param {Element} element - The element to extract text from.
|
|
120
|
+
* @returns {string} The concatenated, whitespace-normalised text.
|
|
121
|
+
*/
|
|
122
|
+
function getDeepTextContent(element) {
|
|
123
|
+
if (!element) {
|
|
124
|
+
return '';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const parts = [];
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Walk every child node, descending into open shadow roots.
|
|
131
|
+
* @param {Node} node - Current node in the walk.
|
|
132
|
+
*/
|
|
133
|
+
function walk(node) {
|
|
134
|
+
// If this element has an open shadow root, walk its shadow children
|
|
135
|
+
// instead of (or in addition to) the light DOM children.
|
|
136
|
+
if (node.nodeType === Node.ELEMENT_NODE && node.shadowRoot) {
|
|
137
|
+
for (let sc = node.shadowRoot.firstChild; sc; sc = sc.nextSibling) {
|
|
138
|
+
walk(sc);
|
|
139
|
+
}
|
|
140
|
+
// Also walk slotted light-DOM children
|
|
141
|
+
for (let lc = node.firstChild; lc; lc = lc.nextSibling) {
|
|
142
|
+
walk(lc);
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
148
|
+
const text = node.nodeValue;
|
|
149
|
+
if (text && text.trim()) {
|
|
150
|
+
parts.push(text.trim());
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
156
|
+
for (let child = node.firstChild; child; child = child.nextSibling) {
|
|
157
|
+
walk(child);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
walk(element);
|
|
163
|
+
return parts.join(' ').replace(/\s+/g, ' ').trim();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Checks whether an element or any of its descendants is a custom element
|
|
168
|
+
* (has a hyphen in its tag name per the HTML spec).
|
|
169
|
+
*
|
|
170
|
+
* Useful for detecting elements that *may* contain shadow DOM content
|
|
171
|
+
* which is inaccessible via `textContent` or `querySelectorAll`.
|
|
172
|
+
*
|
|
173
|
+
* @param {Element} element - The element to inspect.
|
|
174
|
+
* @returns {boolean} True if the element itself or a descendant is a custom element.
|
|
175
|
+
*/
|
|
176
|
+
function hasCustomElementDescendant(element) {
|
|
177
|
+
if (!element) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (element.tagName && element.tagName.indexOf('-') !== -1) {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const children = element.querySelectorAll ? element.querySelectorAll('*') : [];
|
|
186
|
+
for (let i = 0; i < children.length; i++) {
|
|
187
|
+
if (children[i].tagName && children[i].tagName.indexOf('-') !== -1) {
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
|
|
114
195
|
module.exports = {
|
|
115
196
|
deepGetElementById,
|
|
116
197
|
deepQuerySelector,
|
|
117
198
|
deepQuerySelectorAll,
|
|
199
|
+
getDeepTextContent,
|
|
200
|
+
hasCustomElementDescendant,
|
|
118
201
|
};
|
package/src/stringUtils.js
CHANGED
|
@@ -167,6 +167,8 @@ const stringUtils = (function () {
|
|
|
167
167
|
|
|
168
168
|
/**
|
|
169
169
|
* Get the actual visible text content of an element, ignoring aria-label.
|
|
170
|
+
* Traverses open shadow DOM boundaries so that text rendered inside
|
|
171
|
+
* web components is included.
|
|
170
172
|
* @param {Element} element - The DOM element
|
|
171
173
|
* @returns {string} The visible text content
|
|
172
174
|
*/
|
|
@@ -174,7 +176,14 @@ const stringUtils = (function () {
|
|
|
174
176
|
if (!element) {
|
|
175
177
|
return '';
|
|
176
178
|
}
|
|
177
|
-
|
|
179
|
+
// Fast path: no shadow roots in subtree
|
|
180
|
+
const text = (element.textContent || '').trim();
|
|
181
|
+
if (text) {
|
|
182
|
+
return text;
|
|
183
|
+
}
|
|
184
|
+
// Slow path: element may contain custom elements with shadow DOM
|
|
185
|
+
const { getDeepTextContent } = require('./shadowDomUtils.js');
|
|
186
|
+
return getDeepTextContent(element);
|
|
178
187
|
}
|
|
179
188
|
|
|
180
189
|
/**
|