fix(js,dom): scope element queries, real timers, form/prepend/equal (#180)
- Element.querySelector / querySelectorAll scope to the receiver subtree via a new query_selector_scoped Rust op, not always document root. Same for DocumentFragment. - setTimeout respects its delay argument via a new async op_sleep. setInterval is a real repeating timer instead of an alias to setTimeout. - document.forms / images / links return live querySelectorAll results instead of hardcoded []. - HTMLFormElement subclasses Element with .elements / .length, and form nodes are wrapped as HTMLFormElement so instanceof works. submit() stays on Element.prototype to preserve the existing submit-event dispatch and form-data navigation path. - Element.prepend has a real implementation. - Node.isEqualNode does a structural compare (nodeType, nodeName, nodeValue, attributes, recursive children) instead of nid identity. - Fix long-standing insert_before arg-order bug: bootstrap.js was passing (parent, new, ref) but _dom only forwards two args, so ref was silently dropped. Pass (new, ref) directly. Same fix in replaceChild. Adds 3 selector unit tests and 6 runtime regression tests covering the JS shim fixes. Closes #105.
This commit is contained in:
1 parent
d4d749d850
commit
71b9b4fbab
4 files changed
+264
-31
No files matched your search
@@ -965,6 +965,117 @@ mod tests {
|
||||
assert_eq!(text, serde_json::json!("BODY_TEXT"));
|
||||
}
|
||||
|
||||
/// Regression for #105: `element.querySelector` and `querySelectorAll`
|
||||
/// must scope to the receiver's subtree, not the whole document.
|
||||
#[test]
|
||||
fn element_query_selector_is_scoped_to_subtree() {
|
||||
let mut rt = setup_runtime(
|
||||
r#"<div id="a"><span class="x">in a</span></div><div id="b"><span class="x">in b</span></div>"#,
|
||||
);
|
||||
let text = rt
|
||||
.evaluate("document.getElementById('a').querySelector('.x').textContent")
|
||||
.unwrap();
|
||||
assert_eq!(text, serde_json::json!("in a"));
|
||||
|
||||
let count_in_a = rt
|
||||
.evaluate("document.getElementById('a').querySelectorAll('.x').length")
|
||||
.unwrap();
|
||||
assert_eq!(count_in_a.as_f64().unwrap() as i64, 1);
|
||||
|
||||
// Document-scoped query still sees both.
|
||||
let count_doc = rt.evaluate("document.querySelectorAll('.x').length").unwrap();
|
||||
assert_eq!(count_doc.as_f64().unwrap() as i64, 2);
|
||||
}
|
||||
|
||||
/// Regression for #105: `document.forms` / `images` / `links` must be
|
||||
/// live, not hardcoded `[]`. jQuery 1.x's submit-event setup iterates
|
||||
/// `document.forms` and crashes when it's empty for pages that have forms.
|
||||
#[test]
|
||||
fn document_forms_images_links_are_live() {
|
||||
let mut rt = setup_runtime(
|
||||
r#"<form></form><form></form><img><a href="x">l</a><a>no-href</a>"#,
|
||||
);
|
||||
assert_eq!(rt.evaluate("document.forms.length").unwrap().as_f64().unwrap() as i64, 2);
|
||||
assert_eq!(rt.evaluate("document.images.length").unwrap().as_f64().unwrap() as i64, 1);
|
||||
assert_eq!(rt.evaluate("document.links.length").unwrap().as_f64().unwrap() as i64, 1);
|
||||
}
|
||||
|
||||
/// Regression for #105: `HTMLFormElement` must expose `.elements` so
|
||||
/// frameworks that probe form field collections work.
|
||||
#[test]
|
||||
fn html_form_element_exposes_elements_collection() {
|
||||
let mut rt = setup_runtime(
|
||||
r#"<form id="f"><input name=a><input name=b><textarea></textarea></form>"#,
|
||||
);
|
||||
let n = rt
|
||||
.evaluate("document.getElementById('f').elements.length")
|
||||
.unwrap();
|
||||
assert_eq!(n.as_f64().unwrap() as i64, 3);
|
||||
let is_form = rt
|
||||
.evaluate("document.getElementById('f') instanceof HTMLFormElement")
|
||||
.unwrap();
|
||||
assert_eq!(is_form, serde_json::json!(true));
|
||||
}
|
||||
|
||||
/// Regression for #105: `Element.prepend` must actually insert at the
|
||||
/// start, not silently no-op.
|
||||
#[test]
|
||||
fn element_prepend_inserts_at_start() {
|
||||
let mut rt = setup_runtime(r#"<div id="c"><span>existing</span></div>"#);
|
||||
rt.evaluate(
|
||||
r#"
|
||||
const c = document.getElementById('c');
|
||||
const n = document.createElement('span');
|
||||
n.id = 'first';
|
||||
c.prepend(n);
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let first_id = rt.evaluate("document.getElementById('c').firstChild.id").unwrap();
|
||||
assert_eq!(first_id, serde_json::json!("first"));
|
||||
let count = rt.evaluate("document.getElementById('c').childNodes.length").unwrap();
|
||||
assert_eq!(count.as_f64().unwrap() as i64, 2);
|
||||
}
|
||||
|
||||
/// Regression for #105: `isEqualNode` compares structure, not identity.
|
||||
/// Framework diff algorithms rely on this.
|
||||
#[test]
|
||||
fn is_equal_node_does_structural_compare() {
|
||||
let mut rt = setup_runtime("<html><body></body></html>");
|
||||
let result = rt
|
||||
.evaluate(
|
||||
r#"
|
||||
const a = document.createElement('div'); a.setAttribute('class', 'x'); a.innerHTML = '<span>hi</span>';
|
||||
const b = document.createElement('div'); b.setAttribute('class', 'x'); b.innerHTML = '<span>hi</span>';
|
||||
const c = document.createElement('div'); c.innerHTML = '<span>bye</span>';
|
||||
return [a.isEqualNode(b), a.isEqualNode(c), a.isSameNode(b)];
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(result, serde_json::json!([true, false, false]));
|
||||
}
|
||||
|
||||
/// Regression for the long-standing insert_before arg-order bug noted
|
||||
/// in CLAUDE.md: bootstrap.js was passing (parent, new, ref) but `_dom`
|
||||
/// forwards only two args, silently dropping `ref`. With the fix,
|
||||
/// `insertBefore` actually inserts.
|
||||
#[test]
|
||||
fn insert_before_inserts_node_at_correct_position() {
|
||||
let mut rt = setup_runtime(r#"<div id="p"><span id="b">b</span><span id="c">c</span></div>"#);
|
||||
let order = rt
|
||||
.evaluate(
|
||||
r#"
|
||||
const p = document.getElementById('p');
|
||||
const a = document.createElement('span');
|
||||
a.id = 'a';
|
||||
p.insertBefore(a, document.getElementById('b'));
|
||||
return Array.from(p.children).map(e => e.id).join(',');
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(order, serde_json::json!("a,b,c"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_console_log() {
|
||||
let mut rt = setup_runtime("<html><body></body></html>");
|
||||
|
||||
Reference in new issue
Block a user