HS generator+runtime: nth() dispatch+expect, dom-query-all SX-list passthrough, nth-of-type selector
- generate-sx-tests.py: add_action/add_assertion accept .nth(N) in PW-body
tests so 'find(sel).nth(1).dispatchEvent(...)' lands as a dispatch on
the Nth matching element, and assertions target that same element.
- shared/static/wasm/sx/dom.sx: dom-query-all hands through an already-SX
list unchanged — the bridge often pre-converts NodeLists/arrays to SX
lists, so the host-get 'length' / host-call 'item' loop was returning
empty. Guards node-list=nil and non-list types too.
- tests/hs-run-filtered.js (mock DOM): fnd() understands
':nth-of-type(N)', ':first-of-type', ':last-of-type' by matching the
stripped base selector and returning the correct-indexed sibling.
Covers upstream tests that write 'find("div:nth-of-type(2)")' to
pick the HS-owning element.
- Runtime runtime.sx: hs-sorted-by, hs-fetch format normalizer (JSON/
Object/etc.), nil-safe hs-joined-by/hs-split-by, emit-fetch chain sets
the-result when wrapped in let((it …)).
Net: take 0→6, hide 11→12, show 15→16, fetch 11→15,
collectionExpressions 13→15 (remaining are a WASM JIT bug on
{…} literals inside arrays).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -176,17 +176,30 @@ function parseHTMLFragments(html) {
|
||||
function mt(e,s) {
|
||||
if(!e||!e.tagName)return false;
|
||||
s = s.trim();
|
||||
if(s.startsWith('#'))return e.id===s.slice(1);
|
||||
if(s.startsWith('.'))return e.classList.contains(s.slice(1));
|
||||
if(s.startsWith('[')) {
|
||||
const m = s.match(/^\[([^\]=]+)(?:="([^"]*)")?\]$/);
|
||||
// Strip pseudo-classes we handle at the fnd() level (nth-of-type, first/last-of-type)
|
||||
const pseudo = s.match(/^(.*?):(nth-of-type|first-of-type|last-of-type)(?:\((\d+)\))?$/);
|
||||
const base = pseudo ? pseudo[1] : s;
|
||||
if(base.startsWith('#'))return e.id===base.slice(1);
|
||||
if(base.startsWith('.'))return e.classList.contains(base.slice(1));
|
||||
if(base.startsWith('[')) {
|
||||
const m = base.match(/^\[([^\]=]+)(?:="([^"]*)")?\]$/);
|
||||
if(m) return m[2] !== undefined ? e.getAttribute(m[1]) === m[2] : e.hasAttribute(m[1]);
|
||||
}
|
||||
if(s.includes('.')) { const [tag, cls] = s.split('.'); return e.tagName.toLowerCase() === tag && e.classList.contains(cls); }
|
||||
if(s.includes('#')) { const [tag, id] = s.split('#'); return e.tagName.toLowerCase() === tag && e.id === id; }
|
||||
return e.tagName.toLowerCase() === s.toLowerCase();
|
||||
if(base.includes('.')) { const [tag, cls] = base.split('.'); return e.tagName.toLowerCase() === tag && e.classList.contains(cls); }
|
||||
if(base.includes('#')) { const [tag, id] = base.split('#'); return e.tagName.toLowerCase() === tag && e.id === id; }
|
||||
return e.tagName.toLowerCase() === base.toLowerCase();
|
||||
}
|
||||
function fnd(e,s) {
|
||||
const pseudo = s && typeof s==='string' ? s.match(/^(.*?):(nth-of-type|first-of-type|last-of-type)(?:\((\d+)\))?$/) : null;
|
||||
if (pseudo) {
|
||||
const all = fndAll(e, pseudo[1]);
|
||||
if (pseudo[2] === 'first-of-type') return all[0] || null;
|
||||
if (pseudo[2] === 'last-of-type') return all[all.length - 1] || null;
|
||||
const idx = parseInt(pseudo[3] || '1') - 1;
|
||||
return all[idx] || null;
|
||||
}
|
||||
for(const c of(e.children||[])){if(mt(c,s))return c;const f=fnd(c,s);if(f)return f;} return null;
|
||||
}
|
||||
function fnd(e,s) { for(const c of(e.children||[])){if(mt(c,s))return c;const f=fnd(c,s);if(f)return f;} return null; }
|
||||
function fndAll(e,s) { const r=[];for(const c of(e.children||[])){if(mt(c,s))r.push(c);r.push(...fndAll(c,s));}r.item=function(i){return r[i]||null;};return r; }
|
||||
|
||||
const _body = new El('body');
|
||||
|
||||
Reference in New Issue
Block a user