use javascript::evaluate_script;
fn fix_rust_escapes(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c == '\\' {
if let Some(&'u') = chars.peek() {
chars.next(); if let Some(&'{') = chars.peek() {
chars.next(); let mut hex = String::new();
while let Some(&c2) = chars.peek() {
if c2 == '}' {
chars.next();
break;
}
hex.push(chars.next().expect("unexpected end"));
}
if let Ok(u) = u32::from_str_radix(&hex, 16)
&& let Some(ch) = std::char::from_u32(u)
{
out.push(ch);
continue;
}
out.push_str(&format!("\\u{{{}}}", hex));
} else {
out.push_str("\\u");
}
} else {
out.push('\\');
}
} else {
out.push(c);
}
}
out
}
fn parse_js_json_result(s: &str) -> serde_json::Value {
let inner_json: String =
serde_json::from_str(s).unwrap_or_else(|_| s.trim_matches('"').to_string().replace("\\\"", "\"").replace("\\\\", "\\"));
let fixed_json = fix_rust_escapes(&inner_json);
serde_json::from_str(&fixed_json).unwrap_or_else(|_| panic!("invalid inner json: {inner_json}"))
}
#[test]
fn string_iterator_simple() {
let script = r#"
let out = [];
for (let ch of "abc") { out.push(ch); }
JSON.stringify(out)
"#;
let res = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
assert_eq!(res, "\"[\\\"a\\\",\\\"b\\\",\\\"c\\\"]\"");
}
#[test]
fn string_iterator_unicode() {
let script = r#"
let s = "a𠮷b";
console.log(s);
let out = [];
out.push(s.length);
out.push(s.charCodeAt(0)); out.push(s.charCodeAt(1)); out.push(s.charCodeAt(2)); out.push(s.charCodeAt(3));
for (let ch of s) { out.push(ch); }
JSON.stringify(out)
"#;
let res = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
println!("DEBUG: string_iterator_unicode result: {}", res);
assert_eq!(res, "\"[4,97,55362,57271,98,\\\"a\\\",\\\"𠮷\\\",\\\"b\\\"]\"");
}
#[test]
fn string_char_codes_direct() {
let script = r#"
(function(){
let s = "a𠮷b";
return JSON.stringify([s.length, s.charCodeAt(0), s.charCodeAt(1), s.charCodeAt(2), s.charCodeAt(3)]);
})()
"#;
let res = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
let v = parse_js_json_result(&res);
let arr = v.as_array().expect("expected array");
assert_eq!(arr[0].as_i64().unwrap(), 4);
assert_eq!(arr[1].as_i64().unwrap(), 97);
assert_eq!(arr[2].as_i64().unwrap(), 55362);
assert_eq!(arr[3].as_i64().unwrap(), 57271);
assert_eq!(arr[4].as_i64().unwrap(), 98);
}
#[test]
fn string_charcode_single() {
let script = r#"
(function(){
let s = "a𠮷b";
return s.charCodeAt(1);
})()
"#;
let res = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
assert_eq!(res, "55362");
}
#[test]
fn string_iterator_combining_mark() {
let script = r#"
let s = "y̆";
let out = [];
for (let ch of s) { out.push(ch); }
JSON.stringify(out)
"#;
let res = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
assert_eq!(res, "\"[\\\"y\\\",\\\"\u{306}\\\"]\"");
}
#[test]
fn string_iterator_flag_regional_indicators() {
let script = r#"
let s = "🇨🇦";
let out = [];
for (let ch of s) { out.push(ch.length); out.push(ch.charCodeAt(0)); out.push(ch.charCodeAt(1)); }
JSON.stringify(out)
"#;
let s = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
let s = s.trim_start_matches('"').trim_end_matches('"');
let v = parse_js_json_result(s);
let arr = v.as_array().expect("expected array");
assert_eq!(arr[0].as_i64().unwrap(), 2);
assert_eq!(arr[3].as_i64().unwrap(), 2);
let h1 = arr[1].as_i64().unwrap() as u32;
let l1 = arr[2].as_i64().unwrap() as u32;
let h2 = arr[4].as_i64().unwrap() as u32;
let l2 = arr[5].as_i64().unwrap() as u32;
assert!((0xD800..=0xDBFF).contains(&h1));
assert!((0xDC00..=0xDFFF).contains(&l1));
assert!((0xD800..=0xDBFF).contains(&h2));
assert!((0xDC00..=0xDFFF).contains(&l2));
}
#[test]
fn string_iterator_zwj_sequence() {
let script = r#"
let s = "👩👩👧👦";
let out = [];
for (let ch of s) { out.push(ch); }
JSON.stringify(out)
"#;
let s = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
println!("DEBUG: string_iterator_zwj_sequence result: {}", s);
let s = s.trim_start_matches('"').trim_end_matches('"');
let v = parse_js_json_result(s);
let arr = v.as_array().expect("expected array");
assert!(arr.len() > 1);
for item in arr.iter() {
assert_ne!(item.as_str().unwrap(), "�");
}
}
#[test]
fn string_object_iterator_behaves_same() {
let script = r#"
let s = new String("a𠮷b");
let out = [];
for (let ch of s) { out.push(ch); }
JSON.stringify(out)
"#;
let s = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
let s = s.trim_start_matches('"').trim_end_matches('"');
let v = parse_js_json_result(s);
let arr = v.as_array().expect("expected array");
assert_eq!(arr[0].as_str().unwrap(), "a");
assert_eq!(arr[1].as_str().unwrap(), "𠮷");
assert_eq!(arr[2].as_str().unwrap(), "b");
}