@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.
@@ -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
  };
@@ -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
- return (element.textContent || '').trim();
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
  /**