@unotest/grounder-client 0.10.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/CHANGELOG.md +66 -0
- package/LICENSE +21 -0
- package/README.md +38 -0
- package/dist/dom-walker/index.d.ts +115 -0
- package/dist/dom-walker/index.js +717 -0
- package/dist/index.d.ts +182 -0
- package/dist/index.js +124 -0
- package/dist/types-CMkcL3q9.d.ts +69 -0
- package/package.json +49 -0
|
@@ -0,0 +1,717 @@
|
|
|
1
|
+
// src/dom-walker/injected-helpers.ts
|
|
2
|
+
function createDomHelpers(opts) {
|
|
3
|
+
const MAX_NAME = opts && opts.maxName || 80;
|
|
4
|
+
const FONT_WEIGHT_DESCENDANTS = 4;
|
|
5
|
+
const ariaHiddenAncestors = !opts || opts.ariaHidden !== "self";
|
|
6
|
+
function parentOrHost(node) {
|
|
7
|
+
const parent = node.parentNode;
|
|
8
|
+
if (!parent) return null;
|
|
9
|
+
if (parent instanceof ShadowRoot) return parent.host;
|
|
10
|
+
return parent instanceof Element ? parent : null;
|
|
11
|
+
}
|
|
12
|
+
function rootForScope(el) {
|
|
13
|
+
const root = el.getRootNode();
|
|
14
|
+
return root instanceof ShadowRoot ? root : document;
|
|
15
|
+
}
|
|
16
|
+
function getElementByIdInScope(el, id) {
|
|
17
|
+
return rootForScope(el).getElementById(id);
|
|
18
|
+
}
|
|
19
|
+
function* deepWalk(root) {
|
|
20
|
+
const children = root.children;
|
|
21
|
+
for (let i = 0; i < children.length; i++) {
|
|
22
|
+
const el = children[i];
|
|
23
|
+
yield el;
|
|
24
|
+
const sr = el.shadowRoot;
|
|
25
|
+
if (sr) yield* deepWalk(sr);
|
|
26
|
+
yield* deepWalk(el);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function isVisible(el) {
|
|
30
|
+
if (!el.getClientRects || el.getClientRects().length === 0) return false;
|
|
31
|
+
const cs = window.getComputedStyle(el);
|
|
32
|
+
if (cs.display === "none" || cs.visibility === "hidden") return false;
|
|
33
|
+
if (Number.parseFloat(cs.opacity || "1") < 0.01) return false;
|
|
34
|
+
if (ariaHiddenAncestors) {
|
|
35
|
+
for (let p = el; p; p = parentOrHost(p)) {
|
|
36
|
+
if (p.getAttribute && p.getAttribute("aria-hidden") === "true") return false;
|
|
37
|
+
}
|
|
38
|
+
} else if (el.getAttribute("aria-hidden") === "true") {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const r = el.getBoundingClientRect();
|
|
42
|
+
if (r.width === 0 && r.height === 0) return false;
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
function trim(s) {
|
|
46
|
+
return (s ?? "").replace(/\s+/g, " ").trim();
|
|
47
|
+
}
|
|
48
|
+
function cap(s) {
|
|
49
|
+
return s.length > MAX_NAME ? s.slice(0, MAX_NAME - 1) + "\u2026" : s;
|
|
50
|
+
}
|
|
51
|
+
function cssEscape(s) {
|
|
52
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
53
|
+
}
|
|
54
|
+
function textContentVisible(el) {
|
|
55
|
+
const ht = el;
|
|
56
|
+
if (typeof ht.innerText === "string") return ht.innerText;
|
|
57
|
+
return el.textContent ?? "";
|
|
58
|
+
}
|
|
59
|
+
const INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
|
|
60
|
+
"button",
|
|
61
|
+
"link",
|
|
62
|
+
"checkbox",
|
|
63
|
+
"radio",
|
|
64
|
+
"menuitem",
|
|
65
|
+
"menuitemcheckbox",
|
|
66
|
+
"menuitemradio",
|
|
67
|
+
"tab",
|
|
68
|
+
"option",
|
|
69
|
+
"switch",
|
|
70
|
+
"combobox",
|
|
71
|
+
"listbox",
|
|
72
|
+
"slider",
|
|
73
|
+
"spinbutton",
|
|
74
|
+
"searchbox",
|
|
75
|
+
"textbox"
|
|
76
|
+
]);
|
|
77
|
+
function formFieldRole(el, tag) {
|
|
78
|
+
if (tag === "textarea") return "textbox";
|
|
79
|
+
if (tag === "select") {
|
|
80
|
+
const s = el;
|
|
81
|
+
return s.multiple || s.size > 1 ? "listbox" : "combobox";
|
|
82
|
+
}
|
|
83
|
+
if (tag === "input") {
|
|
84
|
+
const t = (el.type || "text").toLowerCase();
|
|
85
|
+
if (t === "button" || t === "submit" || t === "reset" || t === "image") return "button";
|
|
86
|
+
if (t === "checkbox") return "checkbox";
|
|
87
|
+
if (t === "radio") return "radio";
|
|
88
|
+
if (t === "range") return "slider";
|
|
89
|
+
if (t === "number") return "spinbutton";
|
|
90
|
+
if (t === "search") return "searchbox";
|
|
91
|
+
if (t === "hidden") return null;
|
|
92
|
+
return "textbox";
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
function implicitRole(el) {
|
|
97
|
+
const tag = el.tagName.toLowerCase();
|
|
98
|
+
if (tag === "a") return el.hasAttribute("href") ? "link" : null;
|
|
99
|
+
if (tag === "button") return "button";
|
|
100
|
+
if (tag === "summary") return "button";
|
|
101
|
+
if (tag === "textarea" || tag === "select" || tag === "input") return formFieldRole(el, tag);
|
|
102
|
+
if (tag === "h1" || tag === "h2" || tag === "h3" || tag === "h4" || tag === "h5" || tag === "h6") return "heading";
|
|
103
|
+
if (tag === "nav") return "navigation";
|
|
104
|
+
if (tag === "header") return "banner";
|
|
105
|
+
if (tag === "main") return "main";
|
|
106
|
+
if (tag === "aside") return "complementary";
|
|
107
|
+
if (tag === "footer") return "contentinfo";
|
|
108
|
+
if (tag === "form") return "form";
|
|
109
|
+
if (tag === "article") return "article";
|
|
110
|
+
if (tag === "dialog") return "dialog";
|
|
111
|
+
if (tag === "section") {
|
|
112
|
+
return el.hasAttribute("aria-label") || el.hasAttribute("aria-labelledby") ? "region" : null;
|
|
113
|
+
}
|
|
114
|
+
if (tag === "ul" || tag === "ol" || tag === "menu") return "list";
|
|
115
|
+
if (tag === "li") return "listitem";
|
|
116
|
+
if (tag === "table") return "table";
|
|
117
|
+
if (tag === "tbody" || tag === "thead" || tag === "tfoot") return "rowgroup";
|
|
118
|
+
if (tag === "tr") return "row";
|
|
119
|
+
if (tag === "td") return "cell";
|
|
120
|
+
if (tag === "th") {
|
|
121
|
+
const scope = (el.getAttribute("scope") || "").toLowerCase();
|
|
122
|
+
return scope === "row" ? "rowheader" : "columnheader";
|
|
123
|
+
}
|
|
124
|
+
if (tag === "p") return "paragraph";
|
|
125
|
+
if (tag === "figure") return "figure";
|
|
126
|
+
if (tag === "figcaption") return "caption";
|
|
127
|
+
if (tag === "img") {
|
|
128
|
+
const alt = el.getAttribute("alt");
|
|
129
|
+
if (alt === "") return null;
|
|
130
|
+
return "img";
|
|
131
|
+
}
|
|
132
|
+
if (tag === "dl") return "list";
|
|
133
|
+
if (tag === "dt") return "term";
|
|
134
|
+
if (tag === "dd") return "definition";
|
|
135
|
+
if (tag === "code" || tag === "pre") return "code";
|
|
136
|
+
if (tag === "blockquote") return "blockquote";
|
|
137
|
+
if (tag === "hr") return "separator";
|
|
138
|
+
if (tag === "details" || tag === "fieldset") return "group";
|
|
139
|
+
if (tag === "iframe") return "iframe";
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
function computeRole(el) {
|
|
143
|
+
const explicit = el.getAttribute("role");
|
|
144
|
+
if (explicit) return explicit.split(/\s+/)[0].toLowerCase();
|
|
145
|
+
return implicitRole(el);
|
|
146
|
+
}
|
|
147
|
+
function nameFromLabelledby(el, seen, resolve) {
|
|
148
|
+
const labelledby = el.getAttribute("aria-labelledby");
|
|
149
|
+
if (!labelledby) return "";
|
|
150
|
+
const parts = [];
|
|
151
|
+
for (const id of labelledby.split(/\s+/)) {
|
|
152
|
+
if (!id) continue;
|
|
153
|
+
const ref = getElementByIdInScope(el, id);
|
|
154
|
+
if (ref && !seen.has(ref)) {
|
|
155
|
+
const n = resolve(ref, seen);
|
|
156
|
+
if (n) parts.push(n);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const joined = trim(parts.join(" "));
|
|
160
|
+
return joined ? cap(joined) : "";
|
|
161
|
+
}
|
|
162
|
+
function svgAccname(svg, seen, resolve) {
|
|
163
|
+
const fromLabelledby = nameFromLabelledby(svg, seen, resolve);
|
|
164
|
+
if (fromLabelledby) return fromLabelledby;
|
|
165
|
+
const ariaLabel = trim(svg.getAttribute("aria-label"));
|
|
166
|
+
if (ariaLabel) return cap(ariaLabel);
|
|
167
|
+
for (const child of Array.from(svg.children)) {
|
|
168
|
+
if (child.tagName.toLowerCase() === "title") {
|
|
169
|
+
const t = trim(child.textContent);
|
|
170
|
+
if (t) return cap(t);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return "";
|
|
175
|
+
}
|
|
176
|
+
function formControlName(el) {
|
|
177
|
+
const tag = el.tagName.toLowerCase();
|
|
178
|
+
const id = el.id;
|
|
179
|
+
if (id) {
|
|
180
|
+
const lbl = rootForScope(el).querySelector(`label[for="${cssEscape(id)}"]`);
|
|
181
|
+
if (lbl) {
|
|
182
|
+
const n = trim(textContentVisible(lbl));
|
|
183
|
+
if (n) return cap(n);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
let p = parentOrHost(el);
|
|
187
|
+
while (p) {
|
|
188
|
+
if (p.tagName.toLowerCase() === "label") {
|
|
189
|
+
const n = trim(textContentVisible(p));
|
|
190
|
+
if (n) return cap(n);
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
p = parentOrHost(p);
|
|
194
|
+
}
|
|
195
|
+
if (tag === "input") {
|
|
196
|
+
const it = el.type?.toLowerCase();
|
|
197
|
+
if (it === "button" || it === "submit" || it === "reset") {
|
|
198
|
+
const v = trim(el.value);
|
|
199
|
+
if (v) return cap(v);
|
|
200
|
+
}
|
|
201
|
+
const ph = trim(el.placeholder);
|
|
202
|
+
if (ph) return cap(ph);
|
|
203
|
+
}
|
|
204
|
+
const t = trim(el.getAttribute("title"));
|
|
205
|
+
if (t) return cap(t);
|
|
206
|
+
return "";
|
|
207
|
+
}
|
|
208
|
+
function composedAccname(el, seen) {
|
|
209
|
+
const parts = [];
|
|
210
|
+
for (const child of Array.from(el.childNodes)) {
|
|
211
|
+
if (child.nodeType === 3) {
|
|
212
|
+
const t = trim(child.nodeValue);
|
|
213
|
+
if (t) parts.push(t);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (child.nodeType !== 1) continue;
|
|
217
|
+
const ce = child;
|
|
218
|
+
if (!isVisible(ce) || seen.has(ce)) continue;
|
|
219
|
+
seen.add(ce);
|
|
220
|
+
const tag = ce.tagName.toLowerCase();
|
|
221
|
+
if (tag === "img") {
|
|
222
|
+
const alt = trim(ce.getAttribute("alt"));
|
|
223
|
+
if (alt) parts.push(alt);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (tag === "svg") {
|
|
227
|
+
const sn = svgAccname(ce, seen, accname);
|
|
228
|
+
if (sn) parts.push(sn);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const sub = composedAccname(ce, seen);
|
|
232
|
+
if (sub) parts.push(sub);
|
|
233
|
+
}
|
|
234
|
+
return trim(parts.join(" "));
|
|
235
|
+
}
|
|
236
|
+
const NO_CONTENT_NAME_TAGS = /* @__PURE__ */ new Set([
|
|
237
|
+
"form",
|
|
238
|
+
"nav",
|
|
239
|
+
"header",
|
|
240
|
+
"main",
|
|
241
|
+
"aside",
|
|
242
|
+
"footer",
|
|
243
|
+
"section",
|
|
244
|
+
"article",
|
|
245
|
+
"dialog",
|
|
246
|
+
"ul",
|
|
247
|
+
"ol",
|
|
248
|
+
"menu",
|
|
249
|
+
"li",
|
|
250
|
+
"table",
|
|
251
|
+
"thead",
|
|
252
|
+
"tbody",
|
|
253
|
+
"tfoot",
|
|
254
|
+
"tr",
|
|
255
|
+
"td",
|
|
256
|
+
"th",
|
|
257
|
+
"p",
|
|
258
|
+
"code",
|
|
259
|
+
"pre",
|
|
260
|
+
"blockquote",
|
|
261
|
+
"details",
|
|
262
|
+
"dl",
|
|
263
|
+
"dt",
|
|
264
|
+
"dd"
|
|
265
|
+
]);
|
|
266
|
+
function makeAccname(hostLanguageName) {
|
|
267
|
+
const accnameFn = (el, visited) => {
|
|
268
|
+
const seen = visited ?? /* @__PURE__ */ new Set();
|
|
269
|
+
if (seen.has(el)) return "";
|
|
270
|
+
seen.add(el);
|
|
271
|
+
const fromLabelledby = nameFromLabelledby(el, seen, accnameFn);
|
|
272
|
+
if (fromLabelledby) return fromLabelledby;
|
|
273
|
+
const ariaLabel = trim(el.getAttribute("aria-label"));
|
|
274
|
+
if (ariaLabel) return cap(ariaLabel);
|
|
275
|
+
const tag = el.tagName.toLowerCase();
|
|
276
|
+
if (tag === "input" || tag === "textarea" || tag === "select") return formControlName(el);
|
|
277
|
+
return hostLanguageName(el, tag, seen, accnameFn);
|
|
278
|
+
};
|
|
279
|
+
return accnameFn;
|
|
280
|
+
}
|
|
281
|
+
const accname = makeAccname((el, tag, seen) => {
|
|
282
|
+
if (tag === "button" || tag === "a" || tag === "summary") {
|
|
283
|
+
const composed = composedAccname(el, seen);
|
|
284
|
+
if (composed) return cap(composed);
|
|
285
|
+
const t = trim(el.getAttribute("title"));
|
|
286
|
+
if (t) return cap(t);
|
|
287
|
+
return "";
|
|
288
|
+
}
|
|
289
|
+
if (tag === "h1" || tag === "h2" || tag === "h3" || tag === "h4" || tag === "h5" || tag === "h6") {
|
|
290
|
+
return cap(trim(textContentVisible(el)));
|
|
291
|
+
}
|
|
292
|
+
if (tag === "img") return cap(trim(el.getAttribute("alt")));
|
|
293
|
+
if (tag === "iframe") return cap(trim(el.getAttribute("title")));
|
|
294
|
+
if (tag === "figure") {
|
|
295
|
+
for (const child of Array.from(el.children)) {
|
|
296
|
+
if (child.tagName.toLowerCase() === "figcaption") {
|
|
297
|
+
const t = trim(textContentVisible(child));
|
|
298
|
+
if (t) return cap(t);
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return "";
|
|
303
|
+
}
|
|
304
|
+
if (tag === "fieldset") {
|
|
305
|
+
for (const child of Array.from(el.children)) {
|
|
306
|
+
if (child.tagName.toLowerCase() === "legend") {
|
|
307
|
+
const t = trim(textContentVisible(child));
|
|
308
|
+
if (t) return cap(t);
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return "";
|
|
313
|
+
}
|
|
314
|
+
if (NO_CONTENT_NAME_TAGS.has(tag)) return "";
|
|
315
|
+
const inner = trim(textContentVisible(el));
|
|
316
|
+
if (inner) return cap(inner);
|
|
317
|
+
return cap(trim(el.getAttribute("title")));
|
|
318
|
+
});
|
|
319
|
+
function ariaStateFlags(el) {
|
|
320
|
+
const flags = {};
|
|
321
|
+
const tag = el.tagName.toLowerCase();
|
|
322
|
+
const ariaChecked = el.getAttribute("aria-checked");
|
|
323
|
+
if (ariaChecked === "true") flags.checked = true;
|
|
324
|
+
else if (ariaChecked === "mixed") flags.checked = "mixed";
|
|
325
|
+
else if (tag === "input" && (el.type === "checkbox" || el.type === "radio") && el.checked) {
|
|
326
|
+
flags.checked = true;
|
|
327
|
+
}
|
|
328
|
+
const ariaPressed = el.getAttribute("aria-pressed");
|
|
329
|
+
if (ariaPressed === "true") flags.pressed = true;
|
|
330
|
+
else if (ariaPressed === "mixed") flags.pressed = "mixed";
|
|
331
|
+
if (el.getAttribute("aria-expanded") === "true") flags.expanded = true;
|
|
332
|
+
if (el.getAttribute("aria-selected") === "true") flags.selected = true;
|
|
333
|
+
else if (tag === "option" && el.selected) flags.selected = true;
|
|
334
|
+
if (rootForScope(el).activeElement === el) flags.active = true;
|
|
335
|
+
return flags;
|
|
336
|
+
}
|
|
337
|
+
function headingLevel(el) {
|
|
338
|
+
const tag = el.tagName.toLowerCase();
|
|
339
|
+
let level;
|
|
340
|
+
if (tag === "h1") level = 1;
|
|
341
|
+
else if (tag === "h2") level = 2;
|
|
342
|
+
else if (tag === "h3") level = 3;
|
|
343
|
+
else if (tag === "h4") level = 4;
|
|
344
|
+
else if (tag === "h5") level = 5;
|
|
345
|
+
else if (tag === "h6") level = 6;
|
|
346
|
+
const ariaLevel = el.getAttribute("aria-level");
|
|
347
|
+
if (ariaLevel) {
|
|
348
|
+
const n = Number.parseInt(ariaLevel, 10);
|
|
349
|
+
if (Number.isFinite(n) && n > 0) level = n;
|
|
350
|
+
}
|
|
351
|
+
return level;
|
|
352
|
+
}
|
|
353
|
+
function collectState(el, role) {
|
|
354
|
+
const state = {};
|
|
355
|
+
if (el.getAttribute("aria-disabled") === "true" || el.disabled) {
|
|
356
|
+
state.disabled = true;
|
|
357
|
+
}
|
|
358
|
+
Object.assign(state, ariaStateFlags(el));
|
|
359
|
+
if (role === "heading") {
|
|
360
|
+
const level = headingLevel(el);
|
|
361
|
+
if (level !== void 0) state.level = level;
|
|
362
|
+
}
|
|
363
|
+
return Object.keys(state).length > 0 ? state : void 0;
|
|
364
|
+
}
|
|
365
|
+
const pageOrigin = (() => {
|
|
366
|
+
try {
|
|
367
|
+
return window.location.origin;
|
|
368
|
+
} catch {
|
|
369
|
+
return "";
|
|
370
|
+
}
|
|
371
|
+
})();
|
|
372
|
+
function normaliseHref(raw) {
|
|
373
|
+
if (!raw) return void 0;
|
|
374
|
+
let href = raw.trim();
|
|
375
|
+
if (!href) return void 0;
|
|
376
|
+
if (pageOrigin && href.startsWith(pageOrigin)) href = href.slice(pageOrigin.length) || "/";
|
|
377
|
+
return href;
|
|
378
|
+
}
|
|
379
|
+
function isInteractive(el, role) {
|
|
380
|
+
if (el.tagName.toLowerCase() === "iframe") return true;
|
|
381
|
+
if (role && INTERACTIVE_ROLES.has(role)) return true;
|
|
382
|
+
if (el.hasAttribute("onclick")) return true;
|
|
383
|
+
const tiRaw = el.getAttribute("tabindex");
|
|
384
|
+
const ti = tiRaw === null ? null : Number.parseInt(tiRaw, 10);
|
|
385
|
+
if (ti !== null && ti >= 0) return true;
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
function idStable(id) {
|
|
389
|
+
if (!id || id.length > 40) return false;
|
|
390
|
+
if (id.startsWith(":") || id.startsWith("__")) return false;
|
|
391
|
+
const digits = id.replace(/\D/g, "").length;
|
|
392
|
+
return !(id.length > 8 && digits * 2 > id.length);
|
|
393
|
+
}
|
|
394
|
+
function stableDataAttr(el) {
|
|
395
|
+
const skip = /^data-(unotest|v-|react|ng-|svelte|qa|testid|sort|shadow|tooltip|toggle|target|bs-|original-title)/;
|
|
396
|
+
const idLike = /(^|[-_])(id|guid|uid|key|code|ref|num|no)$/;
|
|
397
|
+
for (const a of Array.from(el.attributes)) {
|
|
398
|
+
if (!a.name.startsWith("data-") || skip.test(a.name) || !idLike.test(a.name)) continue;
|
|
399
|
+
const v = (a.value ?? "").trim();
|
|
400
|
+
if (!v || v.length > 64) continue;
|
|
401
|
+
if (/^\d+$/.test(v) || idStable(v)) return { name: a.name, value: v };
|
|
402
|
+
}
|
|
403
|
+
return void 0;
|
|
404
|
+
}
|
|
405
|
+
function clickAffordance(el) {
|
|
406
|
+
if (el.hasAttribute("onclick")) return true;
|
|
407
|
+
const cls = el.getAttribute("class") ?? "";
|
|
408
|
+
if (/(^|\s)hover:/.test(cls) || /\bcursor-pointer\b/.test(cls) || /\bclickable\b/.test(cls)) return true;
|
|
409
|
+
try {
|
|
410
|
+
return window.getComputedStyle(el).cursor === "pointer";
|
|
411
|
+
} catch {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
function fontWeight(el) {
|
|
416
|
+
const weightOf = (e) => {
|
|
417
|
+
const raw = window.getComputedStyle(e).fontWeight;
|
|
418
|
+
const n = Number.parseInt(raw, 10);
|
|
419
|
+
if (!Number.isNaN(n)) return n;
|
|
420
|
+
return raw === "bold" || raw === "bolder" ? 700 : 400;
|
|
421
|
+
};
|
|
422
|
+
let max = weightOf(el);
|
|
423
|
+
const kids = el.querySelectorAll("*");
|
|
424
|
+
const cap2 = kids.length < FONT_WEIGHT_DESCENDANTS ? kids.length : FONT_WEIGHT_DESCENDANTS;
|
|
425
|
+
for (let i = 0; i < cap2; i++) {
|
|
426
|
+
const w = weightOf(kids[i]);
|
|
427
|
+
if (w > max) max = w;
|
|
428
|
+
}
|
|
429
|
+
return max;
|
|
430
|
+
}
|
|
431
|
+
function testIdOf(el) {
|
|
432
|
+
return el.getAttribute("data-testid") ?? el.getAttribute("data-qa") ?? void 0;
|
|
433
|
+
}
|
|
434
|
+
const REF_REGEX = /^e\d+$/;
|
|
435
|
+
const htmlEl = document.documentElement;
|
|
436
|
+
let refCounter = Number.parseInt(htmlEl.getAttribute("data-unotest-ref-counter") || "0", 10);
|
|
437
|
+
if (!Number.isFinite(refCounter) || refCounter < 0) refCounter = 0;
|
|
438
|
+
function ensureRef(el, kind) {
|
|
439
|
+
const existing = el.getAttribute("data-unotest-ref");
|
|
440
|
+
if (existing && REF_REGEX.test(existing)) return existing;
|
|
441
|
+
refCounter += 1;
|
|
442
|
+
const ref = "e" + refCounter;
|
|
443
|
+
el.setAttribute("data-unotest-ref", ref);
|
|
444
|
+
el.setAttribute("data-unotest-ref-kind", kind);
|
|
445
|
+
return ref;
|
|
446
|
+
}
|
|
447
|
+
function persistRefCounter() {
|
|
448
|
+
htmlEl.setAttribute("data-unotest-ref-counter", String(refCounter));
|
|
449
|
+
}
|
|
450
|
+
return {
|
|
451
|
+
parentOrHost,
|
|
452
|
+
rootForScope,
|
|
453
|
+
getElementByIdInScope,
|
|
454
|
+
deepWalk,
|
|
455
|
+
isVisible,
|
|
456
|
+
trim,
|
|
457
|
+
cap,
|
|
458
|
+
cssEscape,
|
|
459
|
+
textContentVisible,
|
|
460
|
+
interactiveRoles: INTERACTIVE_ROLES,
|
|
461
|
+
formFieldRole,
|
|
462
|
+
implicitRole,
|
|
463
|
+
computeRole,
|
|
464
|
+
nameFromLabelledby,
|
|
465
|
+
svgAccname,
|
|
466
|
+
formControlName,
|
|
467
|
+
makeAccname,
|
|
468
|
+
accname,
|
|
469
|
+
ariaStateFlags,
|
|
470
|
+
headingLevel,
|
|
471
|
+
collectState,
|
|
472
|
+
pageOrigin,
|
|
473
|
+
normaliseHref,
|
|
474
|
+
isInteractive,
|
|
475
|
+
idStable,
|
|
476
|
+
stableDataAttr,
|
|
477
|
+
clickAffordance,
|
|
478
|
+
testIdOf,
|
|
479
|
+
fontWeight,
|
|
480
|
+
refRegex: REF_REGEX,
|
|
481
|
+
ensureRef,
|
|
482
|
+
persistRefCounter
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
function composeInjectedScript(algorithm, opts = {}) {
|
|
486
|
+
const helperOpts = JSON.stringify(opts.helperOpts ?? {});
|
|
487
|
+
const args = ["__h", ...opts.argLiterals ?? []].join(", ");
|
|
488
|
+
return `(() => { var __name = (fn) => fn; var __h = (${createDomHelpers.toString()})(${helperOpts}); return (${algorithm.toString()})(${args}); })()`;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// src/dom-walker/walker.ts
|
|
492
|
+
function captureRawTree(h) {
|
|
493
|
+
const TOOLTIP_NAME = /(title|tooltip|hint|label|caption|description)/;
|
|
494
|
+
function phraseShaped(v, requireSpace) {
|
|
495
|
+
if (v.length < 3 || v.length > 80) return false;
|
|
496
|
+
if (!/\p{L}/u.test(v)) return false;
|
|
497
|
+
if (/https?:\/\/|^[{[]/.test(v)) return false;
|
|
498
|
+
return !requireSpace || /\s/.test(v);
|
|
499
|
+
}
|
|
500
|
+
function tooltipHint(el) {
|
|
501
|
+
const aria = h.trim(el.getAttribute("aria-description"));
|
|
502
|
+
if (aria) return h.cap(aria);
|
|
503
|
+
let phrase = "";
|
|
504
|
+
for (const a of Array.from(el.attributes)) {
|
|
505
|
+
const name = a.name.toLowerCase();
|
|
506
|
+
if (!name.startsWith("data-") || name.startsWith("data-unotest")) continue;
|
|
507
|
+
const v = h.trim(a.value);
|
|
508
|
+
if (!v) continue;
|
|
509
|
+
if (TOOLTIP_NAME.test(name)) {
|
|
510
|
+
if (phraseShaped(v, false)) return h.cap(v);
|
|
511
|
+
} else if (!phrase && phraseShaped(v, true)) {
|
|
512
|
+
phrase = v;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return h.cap(phrase);
|
|
516
|
+
}
|
|
517
|
+
function controlValue(el) {
|
|
518
|
+
const tag = el.tagName.toLowerCase();
|
|
519
|
+
if (tag === "input") {
|
|
520
|
+
const t = (el.type || "text").toLowerCase();
|
|
521
|
+
if (t === "checkbox" || t === "radio" || t === "button" || t === "submit" || t === "reset" || t === "image" || t === "password" || t === "hidden") {
|
|
522
|
+
return void 0;
|
|
523
|
+
}
|
|
524
|
+
const v = h.trim(el.value);
|
|
525
|
+
return v || void 0;
|
|
526
|
+
}
|
|
527
|
+
if (tag === "textarea") {
|
|
528
|
+
const v = h.trim(el.value);
|
|
529
|
+
return v || void 0;
|
|
530
|
+
}
|
|
531
|
+
if (tag === "select") {
|
|
532
|
+
const s = el;
|
|
533
|
+
const opt = s.selectedOptions && s.selectedOptions.length > 0 ? s.selectedOptions[0] : null;
|
|
534
|
+
const v = h.trim(opt ? opt.label || opt.value : "");
|
|
535
|
+
return v || void 0;
|
|
536
|
+
}
|
|
537
|
+
return void 0;
|
|
538
|
+
}
|
|
539
|
+
const registry = [];
|
|
540
|
+
let elements = 0;
|
|
541
|
+
let textNodes = 0;
|
|
542
|
+
let refs = 0;
|
|
543
|
+
const NORMAL_FONT_WEIGHT = 400;
|
|
544
|
+
function buildElement(el) {
|
|
545
|
+
elements += 1;
|
|
546
|
+
const tag = el.tagName.toLowerCase();
|
|
547
|
+
const role = h.computeRole(el);
|
|
548
|
+
const b = el.getBoundingClientRect();
|
|
549
|
+
const node = {
|
|
550
|
+
kind: "element",
|
|
551
|
+
tag,
|
|
552
|
+
role,
|
|
553
|
+
name: role ? h.accname(el) || tooltipHint(el) : "",
|
|
554
|
+
bounds: { x: Math.round(b.x), y: Math.round(b.y), w: Math.round(b.width), h: Math.round(b.height) },
|
|
555
|
+
children: []
|
|
556
|
+
};
|
|
557
|
+
node.nodeId = registry.length;
|
|
558
|
+
registry.push(el);
|
|
559
|
+
if (role) {
|
|
560
|
+
const state = h.collectState(el, role);
|
|
561
|
+
if (state) node.state = state;
|
|
562
|
+
const weight = h.fontWeight(el);
|
|
563
|
+
if (weight !== NORMAL_FONT_WEIGHT) node.fontWeight = weight;
|
|
564
|
+
}
|
|
565
|
+
const value = controlValue(el);
|
|
566
|
+
if (value !== void 0) node.value = value;
|
|
567
|
+
const testId = h.testIdOf(el);
|
|
568
|
+
if (testId) node.testId = testId;
|
|
569
|
+
const dataId = h.stableDataAttr(el);
|
|
570
|
+
if (dataId) node.dataId = dataId;
|
|
571
|
+
if (tag === "a") {
|
|
572
|
+
const href = h.normaliseHref(el.getAttribute("href"));
|
|
573
|
+
if (href) node.href = href;
|
|
574
|
+
}
|
|
575
|
+
if (h.isInteractive(el, role) || dataId && h.clickAffordance(el)) {
|
|
576
|
+
node.ref = h.ensureRef(el, "i");
|
|
577
|
+
refs += 1;
|
|
578
|
+
}
|
|
579
|
+
consumeChildren(el, node.children);
|
|
580
|
+
return node;
|
|
581
|
+
}
|
|
582
|
+
function consumeChildren(parent, bucket) {
|
|
583
|
+
const sr = parent.shadowRoot;
|
|
584
|
+
if (sr) {
|
|
585
|
+
for (const c of Array.from(sr.childNodes)) consumeNode(c, bucket);
|
|
586
|
+
}
|
|
587
|
+
for (const c of Array.from(parent.childNodes)) consumeNode(c, bucket);
|
|
588
|
+
}
|
|
589
|
+
function consumeNode(n, bucket) {
|
|
590
|
+
if (n.nodeType === 3) {
|
|
591
|
+
const t = h.trim(n.nodeValue);
|
|
592
|
+
if (t) {
|
|
593
|
+
bucket.push({ kind: "text", text: t });
|
|
594
|
+
textNodes += 1;
|
|
595
|
+
}
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
if (n.nodeType !== 1) return;
|
|
599
|
+
const el = n;
|
|
600
|
+
const tag = el.tagName.toLowerCase();
|
|
601
|
+
if (tag === "script" || tag === "style" || tag === "noscript" || tag === "template") return;
|
|
602
|
+
if (tag === "svg") return;
|
|
603
|
+
if (!h.isVisible(el)) return;
|
|
604
|
+
bucket.push(buildElement(el));
|
|
605
|
+
}
|
|
606
|
+
const body = document.body;
|
|
607
|
+
const tree = body ? buildElement(body) : { kind: "element", tag: "body", role: null, name: "", bounds: { x: 0, y: 0, w: 0, h: 0 }, children: [] };
|
|
608
|
+
h.persistRefCounter();
|
|
609
|
+
window["__unotestGrounderNodes"] = registry;
|
|
610
|
+
return {
|
|
611
|
+
url: window.location.href,
|
|
612
|
+
title: document.title || "",
|
|
613
|
+
viewport: { width: window.innerWidth, height: window.innerHeight },
|
|
614
|
+
tree,
|
|
615
|
+
stats: { elements, textNodes, refs }
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// src/dom-walker/index.ts
|
|
620
|
+
var REF_ATTRIBUTE = "data-unotest-ref";
|
|
621
|
+
var REF_COUNTER_ATTRIBUTE = "data-unotest-ref-counter";
|
|
622
|
+
var NODE_REGISTRY_GLOBAL = "__unotestGrounderNodes";
|
|
623
|
+
function domWalkerScript() {
|
|
624
|
+
return composeInjectedScript(captureRawTree, { helperOpts: { ariaHidden: "self" } });
|
|
625
|
+
}
|
|
626
|
+
function nodeByIdScript(nodeId) {
|
|
627
|
+
const id = Math.trunc(nodeId);
|
|
628
|
+
return `(window.${NODE_REGISTRY_GLOBAL} && window.${NODE_REGISTRY_GLOBAL}[${id}] && window.${NODE_REGISTRY_GLOBAL}[${id}].isConnected ? window.${NODE_REGISTRY_GLOBAL}[${id}] : null)`;
|
|
629
|
+
}
|
|
630
|
+
function materializeRefScript(nodeId) {
|
|
631
|
+
const id = Math.trunc(nodeId);
|
|
632
|
+
return `(() => {
|
|
633
|
+
const registry = window.${NODE_REGISTRY_GLOBAL};
|
|
634
|
+
const el = registry && registry[${id}];
|
|
635
|
+
if (!el || !el.isConnected) return null;
|
|
636
|
+
const existing = el.getAttribute(${JSON.stringify(REF_ATTRIBUTE)});
|
|
637
|
+
if (existing && /^e\\d+$/.test(existing)) return existing;
|
|
638
|
+
const htmlEl = document.documentElement;
|
|
639
|
+
let counter = Number.parseInt(htmlEl.getAttribute(${JSON.stringify(REF_COUNTER_ATTRIBUTE)}) || "0", 10);
|
|
640
|
+
if (!Number.isFinite(counter) || counter < 0) counter = 0;
|
|
641
|
+
counter += 1;
|
|
642
|
+
const ref = "e" + counter;
|
|
643
|
+
el.setAttribute(${JSON.stringify(REF_ATTRIBUTE)}, ref);
|
|
644
|
+
el.setAttribute("data-unotest-ref-kind", "n");
|
|
645
|
+
htmlEl.setAttribute(${JSON.stringify(REF_COUNTER_ATTRIBUTE)}, String(counter));
|
|
646
|
+
return ref;
|
|
647
|
+
})()`;
|
|
648
|
+
}
|
|
649
|
+
var HIGHLIGHT_STATE_GLOBAL = "__unotestGrounderHighlight";
|
|
650
|
+
function highlightByNodeIdScript(nodeIds, color = "#ff2d55") {
|
|
651
|
+
const ids = JSON.stringify(nodeIds.map((n) => Math.trunc(n)));
|
|
652
|
+
const c = JSON.stringify(color);
|
|
653
|
+
return `(() => {
|
|
654
|
+
const prev = window.${HIGHLIGHT_STATE_GLOBAL};
|
|
655
|
+
if (prev) {
|
|
656
|
+
prev.box.remove();
|
|
657
|
+
window.removeEventListener("scroll", prev.onMove, true);
|
|
658
|
+
window.removeEventListener("resize", prev.onMove);
|
|
659
|
+
window.${HIGHLIGHT_STATE_GLOBAL} = null;
|
|
660
|
+
}
|
|
661
|
+
const registry = window.${NODE_REGISTRY_GLOBAL};
|
|
662
|
+
if (!registry) return 0;
|
|
663
|
+
const els = ${ids}.map((id) => registry[id]).filter((el) => el && el.isConnected);
|
|
664
|
+
if (els.length === 0) return 0;
|
|
665
|
+
els[0].scrollIntoView({ block: "center", behavior: "instant" });
|
|
666
|
+
const box = document.createElement("div");
|
|
667
|
+
box.style.cssText = "position:fixed;left:0;top:0;width:0;height:0;z-index:2147483647;pointer-events:none;";
|
|
668
|
+
const divs = els.map(() => {
|
|
669
|
+
const d = document.createElement("div");
|
|
670
|
+
d.style.cssText =
|
|
671
|
+
"position:fixed;box-sizing:border-box;border:3px solid " + ${c} + ";" +
|
|
672
|
+
"border-radius:3px;box-shadow:0 0 0 2px rgba(255,255,255,.7);";
|
|
673
|
+
box.appendChild(d);
|
|
674
|
+
return d;
|
|
675
|
+
});
|
|
676
|
+
function onMove() {
|
|
677
|
+
for (let i = 0; i < els.length; i++) {
|
|
678
|
+
const r = els[i].getBoundingClientRect();
|
|
679
|
+
const s = divs[i].style;
|
|
680
|
+
s.left = (r.left - 3) + "px";
|
|
681
|
+
s.top = (r.top - 3) + "px";
|
|
682
|
+
s.width = (r.width + 6) + "px";
|
|
683
|
+
s.height = (r.height + 6) + "px";
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
onMove();
|
|
687
|
+
document.body.appendChild(box);
|
|
688
|
+
window.addEventListener("scroll", onMove, true);
|
|
689
|
+
window.addEventListener("resize", onMove);
|
|
690
|
+
window.${HIGHLIGHT_STATE_GLOBAL} = { box, onMove };
|
|
691
|
+
return els.length;
|
|
692
|
+
})()`;
|
|
693
|
+
}
|
|
694
|
+
function clearHighlightScript() {
|
|
695
|
+
return `(() => {
|
|
696
|
+
const prev = window.${HIGHLIGHT_STATE_GLOBAL};
|
|
697
|
+
if (!prev) return;
|
|
698
|
+
prev.box.remove();
|
|
699
|
+
window.removeEventListener("scroll", prev.onMove, true);
|
|
700
|
+
window.removeEventListener("resize", prev.onMove);
|
|
701
|
+
window.${HIGHLIGHT_STATE_GLOBAL} = null;
|
|
702
|
+
})()`;
|
|
703
|
+
}
|
|
704
|
+
export {
|
|
705
|
+
NODE_REGISTRY_GLOBAL,
|
|
706
|
+
REF_ATTRIBUTE,
|
|
707
|
+
REF_COUNTER_ATTRIBUTE,
|
|
708
|
+
captureRawTree,
|
|
709
|
+
clearHighlightScript,
|
|
710
|
+
composeInjectedScript,
|
|
711
|
+
createDomHelpers,
|
|
712
|
+
domWalkerScript,
|
|
713
|
+
highlightByNodeIdScript,
|
|
714
|
+
materializeRefScript,
|
|
715
|
+
nodeByIdScript
|
|
716
|
+
};
|
|
717
|
+
//# sourceMappingURL=index.js.map
|