use super::*;
use crate::parser::Parser;
fn run(src: &str) -> String {
let program = Parser::parse_program(src).expect("parse");
let mut interp = Interp::new();
let value = interp.run(&program).expect("exec");
interp.realm().to_display_string(value)
}
#[test]
fn lone_surrogates_round_trip_through_string_ops() {
assert_eq!(run(r#""\uD800".length === 1"#), "true");
assert_eq!(run(r#""\uD800".charCodeAt(0) === 0xD800"#), "true");
assert_eq!(run(r#""\u{D834}".charCodeAt(0) === 0xD834"#), "true");
assert_eq!(
run(
r#""😀".length === 2 && "😀".codePointAt(0) === 0x1F600 && "😀".charCodeAt(0) === 0xD83D"#
),
"true"
);
assert_eq!(
run(r#""a\uD800b".slice(1,2).charCodeAt(0) === 0xD800"#),
"true"
);
assert_eq!(
run(r#""a\uD800b".substring(1,2).charCodeAt(0) === 0xD800"#),
"true"
);
assert_eq!(run(r#""a\uD800b".at(1) === "\uD800""#), "true");
assert_eq!(run(r#""a\uD800b"[1].charCodeAt(0) === 0xD800"#), "true");
assert_eq!(
run(r#""\uD800".charAt(0).charCodeAt(0) === 0xD800"#),
"true"
);
assert_eq!(
run("String.fromCharCode(0xD800).charCodeAt(0) === 0xD800"),
"true"
);
assert_eq!(
run("String.fromCharCode(0xD83D, 0xDE00).codePointAt(0) === 0x1F600"),
"true"
);
}
#[test]
fn string_method_correctness_after_perf_rework() {
assert_eq!(run(r#""abc".charCodeAt(1)"#), "98");
assert_eq!(run(r#""hello".slice(1,3)"#), "el");
assert_eq!(run(r#""hello".indexOf("ll")"#), "2");
assert_eq!(run(r#""hello".lastIndexOf("l")"#), "3");
assert_eq!(run(r#"" hi ".trim()"#), "hi");
assert_eq!(run(r#"" hi".trimStart()"#), "hi");
assert_eq!(run(r#""hi ".trimEnd()"#), "hi");
assert_eq!(run(r#""abcabc".search("ca")"#), "2");
assert_eq!(run(r#""a".localeCompare("b") < 0"#), "true");
assert_eq!(run(r#""a-b-c".replace("-", "+")"#), "a+b-c");
assert_eq!(run(r#""a-b-c".replaceAll("-", "+")"#), "a+b+c");
assert_eq!(run(r#""a-b-c".replaceAll("-", (m,o)=>o)"#), "a1b3c");
assert_eq!(run(r#""😀-x-y".replaceAll("-", (m,o)=>o)"#), "😀2x4y");
assert_eq!(run(r#""\uD800".length === 1"#), "true");
assert_eq!(run(r#""\uD800".charCodeAt(0) === 0xD800"#), "true");
assert_eq!(
run(r#""a\uD800b".slice(1,2).charCodeAt(0) === 0xD800"#),
"true"
);
assert_eq!(run(r#""😀".length"#), "2");
}
#[cfg(feature = "regex")]
#[test]
fn regex_compiled_cache_preserves_behaviour() {
assert_eq!(
run(r#"{
let re = /a(\d)/;
let out = [];
for (let i = 0; i < 5; i++) out.push(re.test("a7") + "" + (re.exec("a7")[1]));
out.join(",")
}"#),
"true7,true7,true7,true7,true7"
);
assert_eq!(
run(r#"{ let re=/(\d+)/g; "a1b22c333".match(re).join(",") }"#),
"1,22,333"
);
assert_eq!(
run(r#"{
let re = /\d/g;
let s = "a1b2";
let idx = [];
re.exec(s); idx.push(re.lastIndex);
re.exec(s); idx.push(re.lastIndex);
re.exec(s); idx.push(re.lastIndex); // miss -> reset to 0
idx.join(",")
}"#),
"2,4,0"
);
assert_eq!(
run(r#"{
let re = /\d/y;
re.lastIndex = 1;
let m = re.test("a1b2");
m + ":" + re.lastIndex
}"#),
"true:2"
);
assert_eq!(run(r#"/x/u.test("x") && !/x/u.global"#), "true");
assert_eq!(run(r#"/x/.test("x") && /x/g.global"#), "true");
assert_eq!(run(r#""😀".match(/./)[0].length"#), "1");
assert_eq!(run(r#""😀".match(/./u)[0].length"#), "2");
assert_eq!(
run(r#"{
let a = /\w+/;
let b = /\w+/g;
let r1 = "foo bar".match(a).length; // non-global: 1 match
let r2 = "foo bar".match(b).length; // global: 2 matches
r1 + "," + r2
}"#),
"1,2"
);
}
#[test]
fn oversized_array_growth_throws_range_error() {
assert_eq!(
run("var a=[1]; try{a[1e9]=1;'noThrow'}catch(e){e.constructor.name}"),
"RangeError"
);
assert_eq!(
run("var a=[1]; a.length=1e9; String(a.length)"),
"1000000000"
);
assert_eq!(
run("var a=[1]; a['length']=1e9; String(a.length)"),
"1000000000"
);
assert_eq!(
run("var a=[1]; try{a.length=4294967296;'noThrow'}catch(e){e.constructor.name}"),
"RangeError"
);
assert_eq!(
run("var a=[1]; a[5]=9; JSON.stringify(a)"),
"[1,null,null,null,null,9]"
);
assert_eq!(
run("var a=[1,2,3,4,5]; a.length=2; JSON.stringify(a)"),
"[1,2]"
);
}
#[test]
fn deep_expression_throws_instead_of_overflowing() {
let handle = std::thread::Builder::new()
.stack_size(64 * 1024 * 1024)
.spawn(|| {
let src = core::iter::repeat_n("1", 20_000)
.collect::<alloc::vec::Vec<_>>()
.join("+");
let program = alloc::boxed::Box::leak(alloc::boxed::Box::new(
Parser::parse_program(&src).expect("parse"),
));
let mut interp = Interp::new();
let threw = matches!(interp.run(program), Err(ExecError::Throw(_)));
core::mem::forget(interp);
threw
})
.expect("spawn")
.join()
.expect("join");
assert!(handle, "deep expression should throw, not abort");
}
#[test]
fn array_buffer_transfer_huge_length_throws() {
assert_eq!(
run(
"var b=new ArrayBuffer(4); try{b.transfer(1e309);'noThrow'}catch(e){e.constructor.name}"
),
"RangeError"
);
assert_eq!(
run("var b=new ArrayBuffer(4); b.transfer(8).byteLength"),
"8"
);
}
#[test]
fn surrogate_search_pad_split_iteration_units() {
assert_eq!(run(r#""😀x".indexOf("x") === 2"#), "true");
assert_eq!(run(r#""😀x".includes("x")"#), "true");
assert_eq!(run(r#""a😀b".lastIndexOf("b") === 3"#), "true");
assert_eq!(run(r#""😀b".startsWith("😀")"#), "true");
assert_eq!(run(r#""a😀".endsWith("😀")"#), "true");
assert_eq!(run(r#""x".padStart(3, "😀").length === 3"#), "true");
assert_eq!(run(r#""x".padEnd(2).length === 2"#), "true");
assert_eq!(run(r#""😀".split("").length === 2"#), "true");
assert_eq!(run(r#""a\uD800b".split("").length === 3"#), "true");
assert_eq!(run(r#"[..."😀"].length === 1"#), "true");
assert_eq!(run(r#"[..."a\uD800b"].length === 3"#), "true");
assert_eq!(run(r#""\uD800".repeat(2).length === 2"#), "true");
assert_eq!(
run(r#""\uD800".repeat(2).charCodeAt(1) === 0xD800"#),
"true"
);
assert_eq!(
run(r#"("a".concat("\uD800")).charCodeAt(1) === 0xD800"#),
"true"
);
}
#[cfg(feature = "regex")]
#[test]
fn regex_u16_code_unit_indices_and_uflag() {
assert_eq!(run(r#"/./u.exec("😀")[0].length === 2"#), "true");
assert_eq!(run(r#"/./u.exec("😀").index === 0"#), "true");
assert_eq!(run(r#"/./.exec("😀")[0].length === 1"#), "true");
assert_eq!(run(r#""😀".match(/./g).length === 2"#), "true");
assert_eq!(run(r#""a😀b".replace(/😀/u, "X") === "aXb""#), "true");
assert_eq!(run(r#""a😀b".search(/b/) === 3"#), "true");
assert_eq!(
run(r#"{ const r=/b/g; r.exec("😀b"); r.lastIndex === 3 }"#),
"true"
);
assert_eq!(
run(r#"[..."a😀b".matchAll(/(.)/gu)].map(m=>m.index).join(",") === "0,1,3""#),
"true"
);
assert_eq!(run(r#""a😀b".split(/😀/).join("|") === "a|b""#), "true");
assert_eq!(
run(r#""x😀y".replace(/(😀)/, "[$1]") === "x[😀]y""#),
"true"
);
assert_eq!(run(r#""a😀b".replace(/😀/, "$`$'") === "aabb""#), "true");
assert_eq!(
run(r#""a\uD800b".replace(/\uD800/u, "X") === "aXb""#),
"true"
);
}
#[test]
fn case_and_normalize_preserve_surrogates() {
assert_eq!(
run(r#""\uD800".toUpperCase().charCodeAt(0) === 0xD800"#),
"true"
);
assert_eq!(
run(r#""\uDC00".toLowerCase().charCodeAt(0) === 0xDC00"#),
"true"
);
assert_eq!(run(r#""a\uD800b".toUpperCase() === "A\uD800B""#), "true");
assert_eq!(run(r#""abc".toUpperCase() === "ABC""#), "true");
assert_eq!(run(r#""ß".toUpperCase() === "SS""#), "true");
assert_eq!(run(r#""ABC".toLowerCase() === "abc""#), "true");
assert_eq!(
run(r#""\uD800".normalize().charCodeAt(0) === 0xD800"#),
"true"
);
assert_eq!(
run(r#""a\uD800é".normalize("NFC").charCodeAt(1) === 0xD800"#),
"true"
);
assert_eq!(run(r#""é".normalize("NFC") === "é""#), "true");
}
#[test]
fn json_preserves_lone_surrogates() {
assert_eq!(run(r#"JSON.stringify("\uD800") === '"\\ud800"'"#), "true");
assert_eq!(run(r#"JSON.stringify("😀") === '"😀"'"#), "true");
assert_eq!(
run(r#"JSON.parse('"\\ud800"').charCodeAt(0) === 0xD800"#),
"true"
);
assert_eq!(run(r#"JSON.parse('"\\ud800"').length === 1"#), "true");
assert_eq!(
run(r#"JSON.parse('"\\ud83d\\ude00"').codePointAt(0) === 0x1F600"#),
"true"
);
assert_eq!(
run(r#"JSON.parse(JSON.stringify("a\uD800b")).charCodeAt(1) === 0xD800"#),
"true"
);
}
#[test]
fn non_surrogate_strings_behave_as_before() {
assert_eq!(run(r#""hello".length"#), "5");
assert_eq!(run(r#""héllo 中".length"#), "7");
assert_eq!(run(r#""abcde".slice(1,3)"#), "bc");
assert_eq!(run(r#""a,b,c".split(",").length"#), "3");
assert_eq!(run(r#""banana".indexOf("na")"#), "2");
assert_eq!(run(r#""banana".lastIndexOf("na")"#), "4");
assert_eq!(run(r#""x".padStart(3, "ab")"#), "abx");
assert_eq!(
run(r#"JSON.stringify({a:1,b:"hi"})"#),
r#"{"a":1,"b":"hi"}"#
);
assert_eq!(run(r#"`a${1}b${2}c`"#), "a1b2c");
}
#[test]
fn limits_override_changes_runtime_caps() {
use crate::limits::Limits;
let src = "'abcde'.repeat(3)"; assert_eq!(eval_source(src).expect("default ok").1, "abcdeabcdeabcde");
let low = Limits {
max_string_len: 10,
..Limits::default()
};
let err = eval_source_with_limits(src, low).expect_err("should exceed length");
assert!(err.contains("Invalid string length"), "unexpected: {err}");
let dict = Limits {
object_dictionary_threshold: 4,
..Limits::default()
};
let keys_src = "let o={}; for(let i=0;i<10;i++) o['k'+i]=i; [Object.keys(o).length, o.k0, o.k9, Object.keys(o)[0]].join(',')";
assert_eq!(
eval_source_with_limits(keys_src, dict).expect("dict ok").1,
"10,0,9,k0"
);
}
#[test]
fn max_eval_depth_override_honored() {
std::thread::Builder::new()
.stack_size(64 * 1024 * 1024)
.spawn(|| {
use crate::limits::Limits;
fn deep_add(depth: usize) -> String {
core::iter::repeat_n("1", depth)
.collect::<alloc::vec::Vec<_>>()
.join("+")
}
let src = deep_add(600);
assert_eq!(eval_source(&src).expect("default ok").1, "600");
let low = Limits {
max_eval_depth: 100,
..Limits::default()
};
let err = eval_source_with_limits(&src, low).expect_err("should exceed eval depth");
assert!(
err.contains("Maximum call stack size exceeded"),
"unexpected error: {err}"
);
})
.expect("spawn")
.join()
.expect("join");
}
#[test]
fn deep_eval_recursion_throws_range_error_catchable() {
let kind = std::thread::Builder::new()
.stack_size(64 * 1024 * 1024)
.spawn(|| {
let deep = core::iter::repeat_n("1", 20_000)
.collect::<alloc::vec::Vec<_>>()
.join("+");
let src =
alloc::format!("try {{ {deep}; 'noThrow' }} catch (e) {{ e.constructor.name }}");
let program = alloc::boxed::Box::leak(alloc::boxed::Box::new(
Parser::parse_program(&src).expect("parse"),
));
let mut interp = Interp::new();
let res = interp.run(program).map(|v| interp.display(v));
core::mem::forget(interp);
res
})
.expect("spawn")
.join()
.expect("join");
assert_eq!(kind.expect("eval ok"), "RangeError");
}
fn out(src: &str) -> String {
let program = Parser::parse_program(src).expect("parse");
let mut interp = Interp::new();
interp.run(&program).expect("exec");
String::from(interp.output())
}
#[cfg(feature = "intl")]
#[test]
fn intl_segmenter_real_grapheme_clusters() {
assert_eq!(
out(
r#"console.log([...new Intl.Segmenter("en").segment("a😀b")].map(s=>s.segment).join("|"))"#
),
"a|😀|b\n"
);
}
#[cfg(feature = "intl")]
#[test]
fn intl_plural_rules_real_cldr_categories() {
assert_eq!(
out(
r#"var p=new Intl.PluralRules("pl");console.log([1,2,5,22].map(n=>p.select(n)).join(","))"#
),
"one,few,many,few\n"
);
}
#[cfg(feature = "intl")]
#[test]
fn intl_collator_real_uca() {
assert_eq!(
out(r#"console.log(new Intl.Collator("en",{numeric:true}).compare("a2","a10"))"#),
"-1\n"
);
assert_eq!(
out(r#"console.log(["é","a","z","b"].sort(new Intl.Collator("en").compare).join(","))"#),
"a,b,é,z\n"
);
}
#[cfg(feature = "intl")]
#[test]
fn intl_numberformat_is_locale_aware() {
assert_eq!(
out(
r#"console.log(new Intl.NumberFormat("de-DE",{style:"currency",currency:"EUR"}).format(1234.5))"#
),
"1.234,50\u{a0}€\n"
);
}
#[cfg(feature = "intl")]
#[test]
fn intl_datetime_is_locale_aware() {
assert_eq!(
out(
r#"console.log(new Intl.DateTimeFormat("de",{timeZone:"UTC",dateStyle:"long"}).format(new Date(Date.UTC(2024,0,15))))"#
),
"15. Januar 2024\n"
);
}
#[cfg(feature = "intl")]
#[test]
fn intl_display_and_list_are_locale_aware() {
assert_eq!(
out(r#"console.log(new Intl.DisplayNames("de",{type:"region"}).of("US"))"#),
"Vereinigte Staaten\n"
);
assert_eq!(
out(r#"console.log(new Intl.ListFormat("es").format(["a","b","c"]))"#),
"a, b y c\n"
);
}
#[test]
fn variables_assignment_and_control_flow() {
assert_eq!(run("let x = 1; let y = 2; x + y"), "3");
assert_eq!(run("let s = 'a'; s += 'b'; s += 'c'; s"), "abc");
assert_eq!(run("let x = 1; { let x = 99; } x"), "1");
assert_eq!(
run("let s = 0; for (let i = 1; i <= 10; i += 1) s += i; s"),
"55"
);
assert_eq!(
run("let s = 0; for (let i = 0; i < 10; i += 1) { if (i === 5) break; s += i; } s"),
"10"
);
}
#[test]
fn functions_and_return() {
assert_eq!(run("function add(a, b) { return a + b; } add(2, 3)"), "5");
assert_eq!(run("let sq = function (x) { return x * x; }; sq(7)"), "49");
assert_eq!(run("let inc = x => x + 1; inc(41)"), "42");
assert_eq!(run("f(10); function f(n) { return n; } f(10)"), "10");
}
#[test]
fn public_snapshot_api_round_trips_across_runtimes() {
let program = Parser::parse_program(
"function makeCounter(start){ var n = start; return function(){ return ++n; }; } makeCounter(0)",
)
.expect("parse");
let mut a = Interp::new();
let f = a.run(&program).expect("exec A");
assert_eq!(a.call(f, &[]).unwrap().as_number(), Some(1.0));
assert_eq!(a.call(f, &[]).unwrap().as_number(), Some(2.0));
let bytes = a.snapshot(&[f]);
drop(a);
let mut b = Interp::new();
let own = b.run(&program).expect("exec B");
let restored = b.restore_snapshot(&bytes).expect("restore");
assert_eq!(restored.len(), 1, "one heap root restored");
assert_eq!(
b.call(restored[0], &[]).unwrap().as_number(),
Some(3.0),
"restored closure resumes from snapshotted state"
);
assert_eq!(
b.call(own, &[]).unwrap().as_number(),
Some(1.0),
"the fresh runtime's own counter is independent"
);
assert!(b.restore_snapshot(b"not a snapshot").is_err());
}
#[test]
fn snapshot_reloads_into_a_fresh_runtime() {
use crate::snapshot::{capture, deserialize, restore, serialize};
let program = Parser::parse_program(
"function makeCounter(start){ var n = start; return function(){ return ++n; }; } makeCounter(0)",
)
.expect("parse");
let mut a = Interp::new();
let f = a.run(&program).expect("exec A");
assert_eq!(a.call(f, &[]).unwrap().as_number(), Some(1.0));
assert_eq!(a.call(f, &[]).unwrap().as_number(), Some(2.0));
let fh = Handle::from_raw(f.as_handle().expect("closure"));
let bytes = serialize(&capture(&a.realm, &[fh]));
drop(a);
let mut b = Interp::new();
let own = b.run(&program).expect("exec B");
let snap = deserialize(&bytes).expect("deserialize");
let restored = restore(&mut b.realm, &snap);
let f2 = NanBox::handle(restored[0].to_raw());
assert_eq!(
b.call(f2, &[]).unwrap().as_number(),
Some(3.0),
"restored closure resumes from the snapshotted state in the new runtime"
);
assert_eq!(
b.call(own, &[]).unwrap().as_number(),
Some(1.0),
"the fresh runtime's own counter is independent of the reloaded one"
);
}
#[test]
fn snapshot_restores_an_executable_closure() {
use crate::snapshot::{capture, deserialize, restore, serialize};
let program = Parser::parse_program(
"function counter(){ var n = 0; return function(){ return ++n; }; } counter()",
)
.expect("parse");
let mut interp = Interp::new();
let f = interp.run(&program).expect("exec");
let fh = Handle::from_raw(f.as_handle().expect("closure handle"));
assert_eq!(interp.call(f, &[]).unwrap().as_number(), Some(1.0));
assert_eq!(interp.call(f, &[]).unwrap().as_number(), Some(2.0));
let snap = capture(&interp.realm, &[fh]);
let snap = deserialize(&serialize(&snap)).expect("deserialize");
let restored = restore(&mut interp.realm, &snap);
let f2 = NanBox::handle(restored[0].to_raw());
assert_eq!(
interp.call(f, &[]).unwrap().as_number(),
Some(3.0),
"original continues"
);
assert_eq!(
interp.call(f2, &[]).unwrap().as_number(),
Some(3.0),
"restored from snapshot"
);
assert_eq!(
interp.call(f2, &[]).unwrap().as_number(),
Some(4.0),
"restored advances"
);
assert_eq!(
interp.call(f, &[]).unwrap().as_number(),
Some(4.0),
"original unaffected by restore"
);
}
#[test]
fn closures_capture_their_scope() {
assert_eq!(
run(
"function adder(n) { return function (x) { return x + n; }; }
let add5 = adder(5);
add5(10)"
),
"15"
);
assert_eq!(
run("function counter() {
let c = 0;
return function () { c += 1; return c; };
}
let next = counter();
next(); next(); next()"),
"3"
);
}
#[test]
fn recursion() {
assert_eq!(
run("function fact(n) { return n <= 1 ? 1 : n * fact(n - 1); } fact(5)"),
"120"
);
assert_eq!(
run("function fib(n) { return n < 2 ? n : fib(n-1) + fib(n-2); } fib(10)"),
"55"
);
}
#[test]
fn higher_order_and_objects() {
assert_eq!(
run("function makeBox(v) {
let value = v;
return { get: function () { return value; },
set: function (x) { value = x; } };
}
let b = makeBox(1);
b.set(99);
b.get()"),
"99"
);
}
#[test]
fn calling_a_non_function_errors() {
let program = Parser::parse_program("let x = 5; x()").unwrap();
let mut interp = Interp::new();
assert!(matches!(interp.run(&program), Err(ExecError::Throw(_))));
}
#[test]
fn try_catch_finally() {
assert_eq!(
run("let r; try { throw 'boom'; r = 'no'; } catch (e) { r = 'caught:' + e; } r"),
"caught:boom"
);
assert_eq!(
run("let r = 'ok'; try { r = 'a'; } catch (e) { r = 'b'; } r"),
"a"
);
assert_eq!(
run(
"let log = ''; try { log += '1'; throw 0; } catch (e) { log += '2'; } finally { log += '3'; } log"
),
"123"
);
assert_eq!(
run("function boom() { throw 'x'; }
let r; try { boom(); } catch (e) { r = 'got:' + e; } r"),
"got:x"
);
assert_eq!(
run("let r = 'a'; try { throw 1; } catch { r = 'b'; } r"),
"b"
);
}
#[test]
fn uncaught_throw_propagates() {
let program = Parser::parse_program("throw 'oops'").unwrap();
let mut interp = Interp::new();
match interp.run(&program) {
Err(ExecError::Throw(v)) => {
assert_eq!(interp.realm().to_display_string(v), "oops");
}
other => panic!("expected a throw, got {other:?}"),
}
}
#[test]
fn finally_return_overrides() {
assert_eq!(
run("function f() { try { return 'a'; } finally { return 'b'; } } f()"),
"b"
);
}
#[test]
fn builtin_functions() {
assert_eq!(run("Math.max(3, 7, 2)"), "7");
assert_eq!(run("Math.min(3, 7, 2)"), "2");
assert_eq!(run("Math.abs(-5)"), "5");
assert_eq!(run("String(42)"), "42");
assert_eq!(run("Number('3.5')"), "3.5");
assert_eq!(run("Boolean(0)"), "false");
assert_eq!(run("Boolean('x')"), "true");
assert_eq!(run("parseInt('42px')"), "42");
assert_eq!(run("parseInt(' -7 ')"), "-7");
assert_eq!(run("typeof Math.max"), "function");
assert_eq!(
run(
"function clamp(x, lo, hi) { return Math.max(lo, Math.min(x, hi)); }
clamp(15, 0, 10)"
),
"10"
);
}
#[test]
fn string_methods() {
assert_eq!(run("'hello'.toUpperCase()"), "HELLO");
assert_eq!(run("'HELLO'.toLowerCase()"), "hello");
assert_eq!(run("' hi '.trim()"), "hi");
assert_eq!(run("'hello'.charAt(1)"), "e");
assert_eq!(run("'hello'.includes('ell')"), "true");
assert_eq!(run("'hello'.indexOf('l')"), "2");
assert_eq!(run("'ab'.repeat(3)"), "ababab");
}
#[test]
fn array_methods() {
assert_eq!(run("let a = [1, 2]; a.push(3); a.join('-')"), "1-2-3");
assert_eq!(run("let a = [1, 2, 3]; a.pop()"), "3");
assert_eq!(run("[1, 2, 3].includes(2)"), "true");
assert_eq!(run("[1, 2, 3].indexOf(3)"), "2");
assert_eq!(run("['a', 'b', 'c'].join(', ')"), "a, b, c");
assert_eq!(
run("let a=[1,2,3,4]; a.splice(1,2,'x'); a.join(',')"),
"1,x,4"
);
assert_eq!(run("[1,2,3,4].splice(1,2).join(',')"), "2,3");
assert_eq!(run("let a=[2,3]; a.unshift(0,1); a.join(',')"), "0,1,2,3");
assert_eq!(
run("let a=[1,2,3]; let f=a.shift(); f + ':' + a.join(',')"),
"1:2,3"
);
assert_eq!(
run(
"let a=[3,1,2]; let s=a.toSorted(function(x,y){return x-y;}); s.join('')+'|'+a.join('')"
),
"123|312"
);
assert_eq!(run("[1,2,3].toReversed().join(',')"), "3,2,1");
assert_eq!(run("[1,2,3].with(1,9).join(',')"), "1,9,3");
assert_eq!(run("[NaN, 1].includes(NaN)"), "true");
assert_eq!(run("[1, 2].includes(NaN)"), "false");
}
#[test]
fn define_property_and_locale_compare() {
assert_eq!(
run("let o={}; Object.defineProperty(o,'x',{value:42}); o.x"),
"42"
);
assert_eq!(
run("let o={n:1}; Object.defineProperty(o,'d',{get:function(){return this.n+1;}}); o.d"),
"2"
);
assert_eq!(
run(
"let o={}; Object.defineProperty(o,'x',{value:7}); Object.getOwnPropertyDescriptor(o,'x').value"
),
"7"
);
assert_eq!(run("'apple'.localeCompare('banana') < 0"), "true");
assert_eq!(run("'x'.localeCompare('x')"), "0");
assert_eq!(run("[0, 0, 0].fill(7).join(',')"), "7,7,7");
assert_eq!(run("[1, 2, 3, 4].fill(9, 1, 3).join(',')"), "1,9,9,4");
assert_eq!(run("[1, 2, 3, 4, 5].fill(0, -2).join(',')"), "1,2,3,0,0");
assert_eq!(
run("['a','b','c'].reduceRight(function(acc,x){ return acc + x; })"),
"cba"
);
assert_eq!(
run("[1,2,3].reduceRight(function(a,x){ return a + x; }, 10)"),
"16"
);
assert_eq!(
run("[1,2,3,4].findLast(function(x){ return x % 2 === 1; })"),
"3"
);
assert_eq!(
run("[1,2,3,4].findLastIndex(function(x){ return x % 2 === 1; })"),
"2"
);
assert_eq!(
run("[2,4,6].findLast(function(x){ return x > 9; })"),
"undefined"
);
assert_eq!(run("[1,2,3,4,5].copyWithin(0,3).join(',')"), "4,5,3,4,5");
assert_eq!(run("[1,[2,[3,[4]]]].flat(2).join(',')"), "1,2,3,4");
}
#[test]
fn math_extras_and_number_coercion() {
assert_eq!(run("Math.hypot(3, 4)"), "5");
assert_eq!(run("Math.cbrt(27)"), "3");
assert_eq!(run("Math.log2(8)"), "3");
assert_eq!(run("Math.log10(1000)"), "3");
assert_eq!(run("(1234.5).toExponential(2)"), "1.23e+3");
assert_eq!(run("Number('0x1F')"), "31");
assert_eq!(run("+'0b101'"), "5");
assert_eq!(run("'0o17' * 1"), "15");
}
#[test]
fn split_limit_and_to_precision() {
assert_eq!(run("'a,b,c,d'.split(',', 2).length"), "2");
assert_eq!(run("'aXbXc'.split('X').join('-')"), "a-b-c");
assert_eq!(run("(123.456).toPrecision(4)"), "123.5");
assert_eq!(run("(255).toString(2)"), "11111111");
}
#[test]
fn object_freeze_family() {
assert_eq!(
run("let o = Object.freeze({ a: 1 }); o.a = 9; o.b = 2; o.a + ',' + (o.b === undefined)"),
"1,true"
);
assert_eq!(run("Object.isFrozen(Object.freeze({}))"), "true");
assert_eq!(run("Object.isFrozen({})"), "false");
assert_eq!(
run("Object.getOwnPropertyNames({ x: 1, y: 2 }).length"),
"2"
);
}
#[test]
fn string_pad_lastindexof_and_number_statics() {
assert_eq!(run("'5'.padEnd(3, '-')"), "5--");
assert_eq!(run("'ab'.padEnd(5)"), "ab ");
assert_eq!(run("'a-b-c'.lastIndexOf('-')"), "3");
assert_eq!(run("'abc'.lastIndexOf('x')"), "-1");
assert_eq!(run("Number.MAX_SAFE_INTEGER"), "9007199254740991");
assert_eq!(run("Number.POSITIVE_INFINITY"), "Infinity");
assert_eq!(run("'abc'.concat('def', '!')"), "abcdef!");
}
#[test]
fn console_log_captures_output() {
let program =
Parser::parse_program("console.log('hi', 42); let x = [1, 2]; console.log('arr:', x);")
.unwrap();
let mut interp = Interp::new();
interp.run(&program).unwrap();
assert_eq!(interp.output(), "hi 42\narr: 1,2\n");
}
#[test]
fn json_getters_tojson_and_date_utc() {
assert_eq!(
run("JSON.stringify({a:1, get b(){ return 2; }})"),
"{\"a\":1,\"b\":2}"
);
assert_eq!(
run("JSON.stringify({v:42, toJSON(){ return {w:this.v}; }})"),
"{\"w\":42}"
);
assert_eq!(run("JSON.stringify([undefined,1])"), "[null,1]");
assert_eq!(run("new Date(Date.UTC(2024,0,15)).getUTCDate()"), "15");
assert_eq!(run("new Date(Date.UTC(2024,0,1)).getUTCDay()"), "1"); assert_eq!(run("new Date(0).toISOString()"), "1970-01-01T00:00:00.000Z");
}
#[test]
fn date_invalid_toisostring() {
assert_eq!(
run("try{new Date(NaN).toISOString();'no'}catch(e){e instanceof RangeError}"),
"true"
);
assert_eq!(run("new Date(NaN).toJSON()"), "null");
assert_eq!(run("JSON.stringify({d:new Date(NaN)})"), "{\"d\":null}");
assert_eq!(
run("new Date(Date.UTC(2020,5,15,10,30,45,123)).toISOString()"),
"2020-06-15T10:30:45.123Z"
);
assert_eq!(
run("try{new Date('garbage').toISOString();'no'}catch(e){e instanceof RangeError}"),
"true"
);
}
#[test]
fn objects_inherit_object_prototype() {
assert_eq!(run("'toString' in {}"), "true");
assert_eq!(run("'hasOwnProperty' in {}"), "true");
assert_eq!(
run("Object.getPrototypeOf({}) === Object.prototype"),
"true"
);
assert_eq!(run("'toString' in Object.create(null)"), "false");
assert_eq!(
run("Object.getPrototypeOf(Object.create(null)) === null"),
"true"
);
assert_eq!(run("Object.keys({a:1,b:2}).join(',')"), "a,b");
assert_eq!(
run("let s=[]; for(let k in {a:1,b:2}) s.push(k); s.join(',')"),
"a,b"
);
assert_eq!(run("JSON.stringify({a:1})"), "{\"a\":1}");
assert_eq!(
run(
"let c=Object.create({i:1}); c.o=2; c.hasOwnProperty('o') + ',' + c.hasOwnProperty('i')"
),
"true,false"
);
}
#[test]
fn object_prototype_tostring_call() {
assert_eq!(run("typeof Object.prototype"), "object");
assert_eq!(run("Object.prototype.toString.call({})"), "[object Object]");
assert_eq!(run("Object.prototype.toString.call([])"), "[object Array]");
assert_eq!(run("Object.prototype.toString.call(null)"), "[object Null]");
assert_eq!(
run("Object.prototype.toString.call(undefined)"),
"[object Undefined]"
);
assert_eq!(
run("Object.prototype.toString.call(function(){})"),
"[object Function]"
);
assert_eq!(
run("Object.prototype.toString.call(/x/)"),
"[object RegExp]"
);
assert_eq!(
run("Object.prototype.toString.call({[Symbol.toStringTag]:'Widget'})"),
"[object Widget]"
);
assert_eq!(
run("Object.prototype.hasOwnProperty.call({a:1},'a')"),
"true"
);
assert_eq!(
run("Object.prototype.hasOwnProperty.call({a:1},'b')"),
"false"
);
assert_eq!(
run("let p={}; Object.prototype.isPrototypeOf.call(p, Object.create(p))"),
"true"
);
assert_eq!(
run("let o={x:1}; Object.prototype.valueOf.call(o)===o"),
"true"
);
}
#[test]
fn webassembly_instantiate_and_call() {
let setup = "var b=[0,0x61,0x73,0x6d,1,0,0,0, 1,7,1,0x60,2,0x7f,0x7f,1,0x7f, 3,2,1,0, 7,7,1,3,0x61,0x64,0x64,0,0, 0xa,9,1,7,0,0x20,0,0x20,1,0x6a,0xb]; var e=new WebAssembly.Instance(new WebAssembly.Module(b)).exports; ";
assert_eq!(run(&alloc::format!("{setup} typeof e.add")), "function");
assert_eq!(run(&alloc::format!("{setup} e.add(20,22)")), "42");
assert_eq!(run(&alloc::format!("{setup} e.add(-5,8)")), "3");
}
#[test]
fn webassembly_validate_builtin() {
assert_eq!(run("typeof WebAssembly"), "object");
assert_eq!(run("typeof WebAssembly.validate"), "function");
assert_eq!(
run("WebAssembly.validate([0,0x61,0x73,0x6d,1,0,0,0])"),
"true"
);
assert_eq!(run("WebAssembly.validate([0,0,0,0,1,0,0,0])"), "false");
assert_eq!(run("WebAssembly.validate([0,0x61])"), "false");
}
#[test]
fn object_default_tostring_and_tag() {
assert_eq!(run("({}).toString()"), "[object Object]");
assert_eq!(run("'abc'.toString()"), "abc");
assert_eq!(run("({a:1}).valueOf().a"), "1");
assert_eq!(run("({toString(){return 'custom';}}).toString()"), "custom");
assert_eq!(
run("Object.create({toString(){return 'base';}}).toString()"),
"base"
);
assert_eq!(
run("({[Symbol.toStringTag]:'Widget'}).toString()"),
"[object Widget]"
);
assert_eq!(run("typeof Symbol.toStringTag"), "symbol");
assert_eq!(run("typeof Symbol.species"), "symbol");
assert_eq!(
run("let o={[Symbol.toStringTag]:'X'}; Object.getOwnPropertySymbols(o).length"),
"1"
);
assert_eq!(run("[1,2,3].toString()"), "1,2,3");
assert_eq!(run("new Error('x').toString()"), "Error: x");
}
#[test]
fn strict_getter_only_write() {
assert_eq!(run("let o={get x(){return 1;}}; o.x=2; o.x"), "1");
assert_eq!(
run(
"(function(){'use strict'; let o={get x(){return 1;}}; try{o.x=2;return 'no';}catch(e){return e instanceof TypeError?'te':'other';}})()"
),
"te"
);
assert_eq!(
run(
"(function(){'use strict'; let o={_v:0,get x(){return this._v;},set x(v){this._v=v*2;}}; o.x=5; return o.x;})()"
),
"10"
);
assert_eq!(
run(
"(function(){'use strict'; let o=Object.create({get y(){return 9;}}); try{o.y=1;return 'no';}catch(e){return e instanceof TypeError?'te':'other';}})()"
),
"te"
);
}
#[test]
fn reduce_empty_throws_typeerror() {
assert_eq!(
run("try{[].reduce((a,b)=>a+b);'no'}catch(e){e instanceof TypeError}"),
"true"
);
assert_eq!(
run("try{[].reduceRight((a,b)=>a+b);'no'}catch(e){e instanceof TypeError}"),
"true"
);
assert_eq!(run("[].reduce((a,b)=>a+b, 99)"), "99");
assert_eq!(run("[42].reduce((a,b)=>a+b)"), "42");
assert_eq!(run("[1,2,3,4].reduce((a,b)=>a+b)"), "10");
}
#[test]
fn function_names_tostring_bound() {
assert_eq!(run("let myFn=function(){}; myFn.name"), "myFn");
assert_eq!(run("let arrow=()=>1; arrow.name"), "arrow");
assert_eq!(run("let x=function inner(){}; x.name"), "inner");
assert_eq!(
run("let o={method(){},fn:()=>1}; o.method.name + ':' + o.fn.name"),
"method:fn"
);
assert_eq!(run("typeof (function f(){}).toString()"), "string");
assert_eq!(
run("(function f(){}).toString().indexOf('function')>=0"),
"true"
);
assert_eq!(run("function t(a,b,c){} t.bind(null,1).length"), "2");
assert_eq!(run("function t(a,b,c){} t.bind(null,1,2,3,4).length"), "0");
assert_eq!(run("function t(){} t.bind(null).name"), "bound t");
}
#[test]
fn proto_accessor() {
assert_eq!(
run("let p={greet(){return 'hi';}}; let o=Object.create(p); o.__proto__===p"),
"true"
);
assert_eq!(
run("let p={greet(){return 'hi';}}; Object.create(p).greet()"),
"hi"
);
assert_eq!(
run("let o={}; o.__proto__={hello(){return 'yo';}}; o.hello()"),
"yo"
);
assert_eq!(
run("let b={getX(){return this.x;}}; let o={}; o.__proto__=b; o.x=42; o.getX()"),
"42"
);
assert_eq!(
run("let b={getX(){return this.x;}}; let n={__proto__:b, x:5}; n.getX()"),
"5"
);
assert_eq!(
run("typeof ({__proto__(){return 1;}}).__proto__"),
"function"
);
assert_eq!(
run("let o={__proto__:{}}; o.__proto__=null; Object.getPrototypeOf(o)===null"),
"true"
);
assert_eq!(
run("let p={g(){return 1;}}; let o=Object.create(p); o.__proto__=5; o.g()"),
"1"
);
}
#[test]
fn proxy_passthrough_keys() {
assert_eq!(run("Object.keys(new Proxy({a:1,b:2},{})).join(',')"), "a,b");
assert_eq!(
run("Object.values(new Proxy({a:1,b:2},{})).join(',')"),
"1,2"
);
assert_eq!(
run("Object.entries(new Proxy({a:1,b:2},{})).map(e=>e.join(':')).join(',')"),
"a:1,b:2"
);
assert_eq!(
run("Object.keys(new Proxy(new Proxy({x:1},{}),{})).join(',')"),
"x"
);
}
#[test]
fn bigint_as_uintn_intn() {
assert_eq!(run("BigInt.asUintN(8, 256n)"), "0");
assert_eq!(run("BigInt.asUintN(8, -1n)"), "255");
assert_eq!(run("BigInt.asUintN(4, -1n)"), "15");
assert_eq!(run("BigInt.asUintN(64, 18446744073709551617n)"), "1");
assert_eq!(run("BigInt.asIntN(8, 200n)"), "-56");
assert_eq!(run("BigInt.asIntN(8, 128n)"), "-128");
assert_eq!(run("BigInt.asIntN(8, 127n)"), "127");
assert_eq!(run("BigInt.asIntN(16, 40000n)"), "-25536");
assert_eq!(run("BigInt.asIntN(32, 4294967295n)"), "-1");
assert_eq!(
run("BigInt.asIntN(128, 12345678901234567890n)"),
"12345678901234567890"
);
}
#[test]
fn json_number_serialization() {
assert_eq!(run("JSON.stringify(-0)"), "0");
assert_eq!(run("JSON.stringify([-0])"), "[0]");
assert_eq!(run("JSON.stringify({x:-0})"), "{\"x\":0}");
assert_eq!(run("JSON.stringify(1e21)"), "1e+21");
assert_eq!(run("JSON.stringify(1e-7)"), "1e-7");
assert_eq!(run("JSON.stringify(1e20)"), "100000000000000000000");
assert_eq!(run("JSON.stringify(0.001)"), "0.001");
assert_eq!(run("JSON.stringify([NaN,Infinity])"), "[null,null]");
assert_eq!(
run("JSON.stringify([-0,1e21,0.001,-42])"),
"[0,1e+21,0.001,-42]"
);
}
#[test]
fn json_stringify_replacer() {
assert_eq!(
run("JSON.stringify({a:1,b:2}, function(k,v){ return k==='b'?undefined:v; })"),
"{\"a\":1}"
);
assert_eq!(
run("JSON.stringify({x:{n:1}}, function(k,v){ return typeof v==='number'?v+5:v; })"),
"{\"x\":{\"n\":6}}"
);
assert_eq!(
run("JSON.stringify({a:1,b:2,c:3}, ['a','c'])"),
"{\"a\":1,\"c\":3}"
);
assert_eq!(
run("JSON.stringify({keep:{a:1,b:2},drop:9}, ['keep','a'])"),
"{\"keep\":{\"a\":1}}"
);
}
#[test]
fn json_parse_reviver() {
assert_eq!(
run(
"let o = JSON.parse('{\"a\":1,\"b\":2}', function(k,v){ return typeof v==='number'?v*2:v; }); o.a + ',' + o.b"
),
"2,4"
);
assert_eq!(
run(
"let o = JSON.parse('{\"keep\":1,\"drop\":2}', function(k,v){ return k==='drop'?undefined:v; }); o.keep + ':' + ('drop' in o)"
),
"1:false"
);
assert_eq!(
run("JSON.parse('[1,2,3]', function(k,v){ return typeof v==='number'?v+10:v; }).join(',')"),
"11,12,13"
);
}
#[test]
fn json_stringify() {
assert_eq!(run("JSON.stringify(42)"), "42");
assert_eq!(run("JSON.stringify('hi')"), "\"hi\"");
assert_eq!(run("JSON.stringify(true)"), "true");
assert_eq!(run("JSON.stringify(null)"), "null");
assert_eq!(run("JSON.stringify([1, 2, 3])"), "[1,2,3]");
assert_eq!(
run("JSON.stringify({ a: 1, b: 'x' })"),
"{\"a\":1,\"b\":\"x\"}"
);
assert_eq!(
run("JSON.stringify({ nested: { list: [1, true, null] } })"),
"{\"nested\":{\"list\":[1,true,null]}}"
);
assert_eq!(
run("JSON.stringify({a:1,b:2}, null, 2)"),
"{\n \"a\": 1,\n \"b\": 2\n}"
);
assert_eq!(run("JSON.stringify([1,2], null, '--')"), "[\n--1,\n--2\n]");
assert_eq!(run("JSON.stringify({}, null, 2)"), "{}");
assert_eq!(run("JSON.stringify([], null, 4)"), "[]");
assert_eq!(run("JSON.stringify('a\"b')"), "\"a\\\"b\"");
}
#[test]
fn object_and_array_statics() {
assert_eq!(run("Object.keys({ a: 1, b: 2 }).join(',')"), "a,b");
assert_eq!(run("Object.values({ a: 1, b: 2 }).join(',')"), "1,2");
assert_eq!(run("Array.isArray([1, 2])"), "true");
assert_eq!(run("Array.isArray('nope')"), "false");
assert_eq!(run("Array.isArray({})"), "false");
}
#[cfg(feature = "std")]
#[test]
fn math_float_methods() {
assert_eq!(run("Math.floor(3.7)"), "3");
assert_eq!(run("Math.ceil(3.2)"), "4");
assert_eq!(run("Math.round(3.5)"), "4");
assert_eq!(run("Math.sqrt(144)"), "12");
}
#[test]
fn classes_and_this() {
assert_eq!(
run("class Point {
constructor(x, y) { this.x = x; this.y = y; }
sum() { return this.x + this.y; }
}
let p = new Point(3, 4);
p.sum()"),
"7"
);
assert_eq!(
run("class Counter {
constructor() { this.n = 0; }
inc() { this.n += 1; return this.n; }
}
let c = new Counter();
c.inc(); c.inc(); c.inc()"),
"3"
);
assert_eq!(
run("class Box { value = 42; get() { return this.value; } }
new Box().get()"),
"42"
);
assert_eq!(
run("class Calc {
constructor(v) { this.v = v; }
double() { return this.v * 2; }
quadruple() { return this.double() * 2; }
}
new Calc(5).quadruple()"),
"20"
);
assert_eq!(run("class A {} typeof A"), "function");
assert_eq!(
run("let C = class { constructor() { this.k = 9; } }; new C().k"),
"9"
);
}
#[test]
fn class_getters_setters_statics() {
assert_eq!(
run("class C { constructor(w, h) { this.w = w; this.h = h; }
get area() { return this.w * this.h; } }
new C(3, 4).area"),
"12"
);
assert_eq!(
run("class Temp {
constructor() { this.c = 0; }
get fahrenheit() { return this.c * 9 / 5 + 32; }
set fahrenheit(f) { this.c = (f - 32) * 5 / 9; }
}
let t = new Temp(); t.fahrenheit = 212; t.c"),
"100"
);
assert_eq!(
run(
"class MathX { static square(n) { return n * n; } static pi = 3; }
MathX.square(5) + MathX.pi"
),
"28"
);
assert_eq!(
run("class Point {
constructor(x) { this.x = x; }
static origin() { return new Point(0); }
}
Point.origin().x"),
"0"
);
}
#[test]
fn class_inheritance() {
assert_eq!(
run("class Animal {
constructor(name) { this.name = name; }
describe() { return this.name; }
}
class Dog extends Animal {}
new Dog('Rex').describe()"),
"Rex"
);
assert_eq!(
run("class Animal {
constructor(name) { this.name = name; }
}
class Dog extends Animal {
constructor(name, breed) { super(name); this.breed = breed; }
tag() { return this.name + ':' + this.breed; }
}
new Dog('Rex', 'Lab').tag()"),
"Rex:Lab"
);
assert_eq!(
run("class A { kind() { return 'A'; } }
class B extends A { kind() { return 'B'; } }
new B().kind() + new A().kind()"),
"BA"
);
assert_eq!(
run("class Base { constructor(v) { this.v = v; } }
class Sub extends Base { get() { return this.v; } }
new Sub(7).get()"),
"7"
);
assert_eq!(
run("class A { constructor() { this.a = 1; } }
class B extends A { constructor() { super(); this.b = 2; } }
class C extends B { constructor() { super(); this.c = 3; } }
let o = new C(); o.a + o.b + o.c"),
"6"
);
}
#[test]
fn object_array_statics_and_number_methods() {
assert_eq!(
run("let t = Object.assign({}, { a: 1 }, { b: 2 }); t.a + t.b"),
"3"
);
assert_eq!(
run("Object.entries({ a: 1, b: 2 }).map(e => e[0] + '=' + e[1]).join(',')"),
"a=1,b=2"
);
assert_eq!(run("Array.from('abc').join('-')"), "a-b-c");
assert_eq!(
run("Array.from([1, 2, 3]).map(x => x * 2).join(',')"),
"2,4,6"
);
assert_eq!(run("Array.of(1, 2, 3).join(',')"), "1,2,3");
assert_eq!(run("(255).toString()"), "255");
}
#[cfg(feature = "std")]
#[test]
fn number_tofixed() {
assert_eq!(run("(3.14159).toFixed(2)"), "3.14");
assert_eq!(run("(1).toFixed(3)"), "1.000");
}
#[test]
fn promises_and_microtasks() {
let out = |src: &str| {
let program = Parser::parse_program(src).expect("parse");
let mut interp = Interp::new();
interp.run(&program).expect("exec");
String::from(interp.output())
};
assert_eq!(
out("console.log('sync');
Promise.resolve(42).then(v => console.log('got:' + v));"),
"sync\ngot:42\n"
);
assert_eq!(
out("Promise.resolve(1).then(v => v + 1).then(v => v * 10).then(v => console.log(v));"),
"20\n"
);
assert_eq!(
out("Promise.reject('boom').catch(e => console.log('caught:' + e));"),
"caught:boom\n"
);
assert_eq!(
out("Promise.resolve(1).then(() => { throw 'x'; }).catch(e => console.log('rej:' + e));"),
"rej:x\n"
);
assert_eq!(
out("new Promise((resolve) => { resolve(7); }).then(v => console.log(v));"),
"7\n"
);
assert_eq!(
out("Promise.resolve(Promise.resolve(99)).then(v => console.log(v));"),
"99\n"
);
assert_eq!(run("typeof Promise.resolve(1)"), "object");
}
#[test]
fn async_await() {
let out = |src: &str| {
let program = Parser::parse_program(src).expect("parse");
let mut interp = Interp::new();
interp.run(&program).expect("exec");
String::from(interp.output())
};
assert_eq!(
out(
"async function f() { let x = await Promise.resolve(10); return x + 5; }
f().then(v => console.log(v));"
),
"15\n"
);
assert_eq!(
out("async function g() {
let a = await Promise.resolve(2);
let b = await Promise.resolve(3);
return a * b;
}
g().then(v => console.log(v));"),
"6\n"
);
assert_eq!(
out("async function h() {
try { await Promise.reject('boom'); return 'no'; }
catch (e) { return 'caught:' + e; }
}
h().then(v => console.log(v));"),
"caught:boom\n"
);
assert_eq!(
out("let f = async (x) => (await x) + 1;
f(41).then(v => console.log(v));"),
"42\n"
);
assert_eq!(run("async function a() {} typeof a"), "function");
assert_eq!(run("async function a() { return 1; } typeof a()"), "object");
}
#[cfg(feature = "regex")]
#[test]
fn regexp() {
assert_eq!(run("/ab+c/.test('xxabbbcyy')"), "true");
assert_eq!(run("/^\\d+$/.test('12345')"), "true");
assert_eq!(run("/^\\d+$/.test('12a45')"), "false");
assert_eq!(run("/hello/i.test('HELLO')"), "true");
assert_eq!(run("new RegExp('a.c').test('axc')"), "true");
assert_eq!(run("/b+/.exec('aabbbc')[0]"), "bbb");
assert_eq!(run("/zzz/.exec('abc') === null"), "true");
assert_eq!(run("'' + /ab/gi"), "/ab/gi");
assert_eq!(run("typeof /x/"), "object");
}
#[test]
fn json_parse() {
assert_eq!(run("JSON.parse('42')"), "42");
assert_eq!(run("JSON.parse('true')"), "true");
assert_eq!(run("JSON.parse('null') === null"), "true");
assert_eq!(run("JSON.parse('\"hi\\\\nthere\"')"), "hi\nthere");
assert_eq!(run("JSON.parse('[1, 2, 3]').length"), "3");
assert_eq!(run("JSON.parse('[1, 2, 3]')[1]"), "2");
assert_eq!(run("JSON.parse('{\"a\": 1, \"b\": 2}').b"), "2");
assert_eq!(
run("JSON.parse('{\"items\": [{\"id\": 7}, {\"id\": 9}]}').items[1].id"),
"9"
);
assert_eq!(
run("let o = JSON.parse('{\"x\": 10, \"y\": 20}'); JSON.stringify(o)"),
"{\"x\":10,\"y\":20}"
);
assert_eq!(run("JSON.parse('-3.5')"), "-3.5");
assert_eq!(
run("try { JSON.parse('{bad}'); 'no'; } catch (e) { 'threw'; }"),
"threw"
);
}
#[test]
fn dates() {
let ts = "1623760245500";
assert_eq!(
run(&alloc::format!("new Date({ts}).getTime()")),
"1623760245500"
);
assert_eq!(run(&alloc::format!("new Date({ts}).getFullYear()")), "2021");
assert_eq!(run(&alloc::format!("new Date({ts}).getMonth()")), "5"); assert_eq!(run(&alloc::format!("new Date({ts}).getDate()")), "15");
assert_eq!(run(&alloc::format!("new Date({ts}).getHours()")), "12");
assert_eq!(run(&alloc::format!("new Date({ts}).getMinutes()")), "30");
assert_eq!(run(&alloc::format!("new Date({ts}).getSeconds()")), "45");
assert_eq!(run(&alloc::format!("new Date({ts}).getDay()")), "2"); assert_eq!(
run(&alloc::format!("new Date({ts}).toISOString()")),
"2021-06-15T12:30:45.500Z"
);
assert_eq!(run("new Date(0).toISOString()"), "1970-01-01T00:00:00.000Z");
assert_eq!(run("new Date(0).getDay()"), "4"); assert_eq!(run("typeof new Date(0)"), "object");
}
#[test]
fn eval_source_entry_point() {
let (out, completion) = eval_source("console.log('hi'); 1 + 2").expect("ok");
assert_eq!(out, "hi\n");
assert_eq!(completion, "3");
let (_, c) = eval_source("let x = 5;").expect("ok");
assert_eq!(c, "undefined");
assert!(eval_source("throw 'boom'").is_err());
assert!(eval_source("let = ;").is_err());
}
#[test]
fn object_is_and_safe_integer_and_computed_methods() {
assert_eq!(run("Object.is(NaN, NaN)"), "true");
assert_eq!(run("Object.is(0, -0)"), "false");
assert_eq!(run("Object.is(-0, -0)"), "true");
assert_eq!(
run("Object.is('a', 'a') + ':' + Object.is(1, 2)"),
"true:false"
);
assert_eq!(run("Number.isSafeInteger(9007199254740991)"), "true");
assert_eq!(run("Number.isSafeInteger(9007199254740992)"), "false");
assert_eq!(run("Number.isSafeInteger(1.5)"), "false");
assert_eq!(
run("let k='go'; class C { [k](){ return 42; } } new C().go()"),
"42"
);
}
#[test]
fn object_spread_of_array_and_string() {
assert_eq!(
run("let o={...[10,20,30]}; o[0] + ':' + o[2] + ':' + Object.keys(o).join(',')"),
"10:30:0,1,2"
);
assert_eq!(run("let o={...'ab'}; o[0] + o[1]"), "ab");
assert_eq!(
run("let o={...[1,2], a:9}; o[0] + ':' + o[1] + ':' + o.a"),
"1:2:9"
);
}
#[test]
fn object_spread_invokes_getters() {
assert_eq!(
run(
"let s={a:1, get b(){ return this.a + 1; }}; let c={...s, d:3}; c.a + ',' + c.b + ',' + c.d"
),
"1,2,3"
);
assert_eq!(
run("JSON.stringify({...{x:1},...{y:2},x:9})"),
"{\"x\":9,\"y\":2}"
);
}
#[test]
fn custom_symbol_iterator() {
let src = "let o = { [Symbol.iterator]() { let i = 0; return { next() { return i < 3 ? { value: i++, done: false } : { value: undefined, done: true }; } }; } };";
assert_eq!(
run(&alloc::format!(
"{src} let s=[]; for (let x of o) s.push(x); s.join(',')"
)),
"0,1,2"
);
assert_eq!(run(&alloc::format!("{src} [...o].join('-')")), "0-1-2");
}
#[test]
fn computed_object_literal_keys() {
assert_eq!(run("let k = 'a' + 'b'; let o = { [k]: 7 }; o.ab"), "7");
assert_eq!(run("let o = { [1 + 1]: 'two' }; o['2']"), "two");
}
#[test]
fn private_class_fields() {
assert_eq!(
run(
"class C { #n = 0; bump(){ this.#n++; return this.#n; } } let c = new C(); c.bump(); c.bump()"
),
"2"
);
assert_eq!(
run("class C { #s = 1; constructor(){ this.p = 2; } } Object.keys(new C()).join(',')"),
"p"
);
}
#[test]
fn bigints() {
assert_eq!(run("typeof 5n"), "bigint");
assert_eq!(run("(2n + 3n).toString()"), "5");
assert_eq!(run("100n * 100n === 10000n"), "true");
assert_eq!(run("2n ** 16n === 65536n"), "true");
assert_eq!(run("10n / 3n === 3n"), "true");
assert_eq!(run("-7n === 0n - 7n"), "true");
assert_eq!(run("BigInt(99) === 99n"), "true");
assert_eq!(run("10n === 10"), "false");
assert_eq!(run("10n == 10"), "true");
assert_eq!(run("!!0n"), "false");
assert_eq!(
run("let r='ok'; try { 1n + 1; } catch (e) { r = 'threw'; } r"),
"threw"
);
assert_eq!(
run("(2n ** 200n).toString()"),
"1606938044258990275541962092341162602522202993782792835301376"
);
assert_eq!(
run("let f=1n; for(let i=1n;i<=25n;i++) f*=i; f.toString()"),
"15511210043330985984000000"
);
assert_eq!(
run("((2n ** 128n) - 1n).toString()"),
"340282366920938463463374607431768211455"
);
assert_eq!(run("(~5n).toString()"), "-6");
assert_eq!(
run("(12n & 10n).toString() + ',' + (12n | 10n) + ',' + (12n ^ 10n)"),
"8,14,6"
);
assert_eq!(run("(-1n & 255n).toString()"), "255");
assert_eq!(run("(((2n ** 100n) | 1n) - (2n ** 100n)).toString()"), "1");
}
#[test]
fn new_on_bound_function() {
assert_eq!(
run(
"function P(x,y){this.x=x;this.y=y;} let B=P.bind(null); let p=new B(3,4); p.x + ':' + p.y"
),
"3:4"
);
assert_eq!(
run("function P(x,y){this.x=x;this.y=y;} let B=P.bind(null,10); new B(20).x"),
"10"
);
assert_eq!(
run("function P(x){this.x=x;} let B=P.bind(null); (new B(1)) instanceof P"),
"true"
);
assert_eq!(
run(
"function P(x,y){this.x=x;this.y=y;} let B=P.bind(null,5).bind(null,6); let p=new B(); p.x + ':' + p.y"
),
"5:6"
);
assert_eq!(
run("class C{constructor(v){this.v=v;}} new (C.bind(null))(42).v"),
"42"
);
assert_eq!(
run("class C{constructor(v){this.v=v;}} new (C.bind(null,7))().v"),
"7"
);
}
#[test]
fn apply_arraylike_and_bound_name() {
assert_eq!(
run("function f(){return arguments.length;} f.apply(null,{length:3,0:1,1:2,2:3})"),
"3"
);
assert_eq!(
run(
"function s(){let t=0;for(let i=0;i<arguments.length;i++)t+=arguments[i];return t;} s.apply(null,{length:2,0:10,1:20})"
),
"30"
);
assert_eq!(run("function foo(){} foo.bind(null).name"), "bound foo");
assert_eq!(
run("function foo(){} foo.bind(null).bind(null).name"),
"bound bound foo"
);
}
#[test]
fn function_length_and_name() {
assert_eq!(run("function f(a, b, c){} f.length"), "3");
assert_eq!(run("function f(){} f.length"), "0");
assert_eq!(run("function f(a, b = 1, c){} f.length"), "1");
assert_eq!(run("function f(a, ...r){} f.length"), "1");
assert_eq!(run("function greet(){} greet.name"), "greet");
assert_eq!(run("let g = function inner(){}; g.name"), "inner");
}
#[test]
fn object_seal_and_extensibility() {
assert_eq!(
run(
"let o={a:1}; Object.preventExtensions(o); o.b=2; o.a=9; String(o.b) + ':' + o.a + ':' + Object.isExtensible(o)"
),
"undefined:9:false"
);
assert_eq!(
run(
"let o={x:1}; Object.seal(o); o.y=2; o.x=5; delete o.x; o.x + ':' + String(o.y) + ':' + Object.isSealed(o)"
),
"5:undefined:true"
);
assert_eq!(
run("let o={a:1}; Object.freeze(o); Object.isSealed(o) + ':' + Object.isExtensible(o)"),
"true:false"
);
}
#[test]
fn non_writable_and_join_nullish() {
assert_eq!(
run(
"let o={}; Object.defineProperty(o,'x',{value:1,writable:false,enumerable:true}); o.x=9; o.x"
),
"1"
);
assert_eq!(
run(
"let o={}; Object.defineProperty(o,'x',{value:1,writable:false}); Object.getOwnPropertyDescriptor(o,'x').writable"
),
"false"
);
assert_eq!(
run(
"let o={a:1}; Object.defineProperty(o,'h',{value:2,enumerable:false}); Object.keys(o).join(',') + ':' + o.h"
),
"a:2"
);
assert_eq!(run("[1,null,2,undefined,3].join('-')"), "1--2--3");
}
#[test]
fn map_group_by_and_well_formed() {
assert_eq!(
run(
"let g=Map.groupBy([1,2,3,4,5], x=>x%2?'odd':'even'); (g instanceof Map) + ':' + g.get('odd').join(',') + ':' + g.size"
),
"true:1,3,5:2"
);
assert_eq!(
run("let k={}; let g=Map.groupBy([1,2], x=>k); g.get(k).join(',')"),
"1,2"
);
assert_eq!(
run("'abc'.isWellFormed() + ':' + '\u{1f600}'.toWellFormed()"),
"true:\u{1f600}"
);
}
#[test]
fn get_own_property_symbols_and_reflect_ownkeys() {
assert_eq!(
run(
"let s=Symbol('k'); let o={a:1}; o[s]=2; let g=Object.getOwnPropertySymbols(o); g.length + ':' + (g[0]===s) + ':' + o[g[0]]"
),
"1:true:2"
);
assert_eq!(run("Object.getOwnPropertySymbols({}).length"), "0");
assert_eq!(
run(
"let s=Symbol('k'); let o={a:1,b:2}; o[s]=3; let k=Reflect.ownKeys(o); k.length + ':' + k[0] + ':' + (k[2]===s)"
),
"3:a:true"
);
}
#[test]
fn assign_and_spread_copy_symbol_keys() {
assert_eq!(
run(
"let s=Symbol('k'); let src={a:1}; src[s]=9; let t=Object.assign({},src); t.a + ':' + t[s]"
),
"1:9"
);
assert_eq!(
run("let s=Symbol('k'); let src={a:1}; src[s]=9; let t={...src}; t[s]"),
"9"
);
assert_eq!(
run("let s=Symbol('k'); let src={a:1}; src[s]=9; Object.keys({...src}).join(',')"),
"a"
);
}
#[test]
fn object_group_by() {
assert_eq!(
run(
"let g=Object.groupBy([1,2,3,4,5], x=>x%2?'odd':'even'); g.odd.join(',') + '|' + g.even.join(',')"
),
"1,3,5|2,4"
);
assert_eq!(run("Object.groupBy(['a','ab','b'], s=>s[0]).a.length"), "2");
assert_eq!(run("Object.keys(Object.groupBy([], x=>x)).length"), "0");
assert_eq!(run("Object.groupBy('aab', c=>c).a.length"), "2");
}
#[test]
fn integer_key_ordering_and_array_tostring() {
assert_eq!(
run("let o={2:'a',1:'b',3:'c'}; Object.keys(o).join(',')"),
"1,2,3"
);
assert_eq!(
run("let o={z:1, 2:2, a:3, 1:4}; Object.keys(o).join(',')"),
"1,2,z,a"
);
assert_eq!(
run("let o={}; o.b=1; o.a=2; Object.keys(o).join(',')"),
"b,a"
);
assert_eq!(
run("Object.values({10:'x',2:'y',1:'z'}).join(',')"),
"z,y,x"
);
assert_eq!(run("['a','b','c'].toString()"), "a,b,c");
assert_eq!(run("[1,[2,3],4].toString()"), "1,2,3,4");
}
#[test]
fn inherited_setter_is_called() {
assert_eq!(
run(
"let base={_d:0, get c(){return this._d;}, set c(v){this._d=v;}}; let d=Object.create(base); d.c=10; d._d + ':' + base._d"
),
"10:0"
);
assert_eq!(
run("let base={get x(){return 1;}}; let d=Object.create(base); d.x=99; d.x"),
"1"
);
assert_eq!(run("let o={a:1}; o.a=2; o.a"), "2");
}
#[test]
fn defineproperty_invariants() {
assert_eq!(
run(
"let o={}; Object.defineProperty(o,'x',{value:1,configurable:false}); try{ Object.defineProperty(o,'x',{value:2}); 'no' }catch(e){ (e instanceof TypeError)+':'+o.x }"
),
"true:1"
);
assert_eq!(
run(
"let o={}; Object.defineProperty(o,'x',{value:1,configurable:true}); Object.defineProperty(o,'x',{value:2,configurable:true}); o.x"
),
"2"
);
assert_eq!(
run(
"let o={}; Object.preventExtensions(o); try{ Object.defineProperty(o,'z',{value:1}); 'no' }catch(e){ e instanceof TypeError }"
),
"true"
);
assert_eq!(
run(
"let o={}; Object.defineProperty(o,'w',{value:1,writable:true,configurable:false}); Object.defineProperty(o,'w',{value:2,writable:true,configurable:false}); o.w"
),
"2"
);
}
#[test]
fn descriptor_reports_configurable() {
assert_eq!(
run(
"let o={}; Object.defineProperty(o,'x',{value:1}); Object.getOwnPropertyDescriptor(o,'x').configurable"
),
"false"
);
assert_eq!(
run(
"let o={}; Object.defineProperty(o,'x',{value:1,configurable:true}); Object.getOwnPropertyDescriptor(o,'x').configurable"
),
"true"
);
assert_eq!(
run("Object.getOwnPropertyDescriptor({a:1},'a').configurable"),
"true"
);
assert_eq!(
run("Object.getOwnPropertyDescriptor(Object.freeze({a:1}),'a').configurable"),
"false"
);
}
#[test]
fn delete_respects_configurable() {
assert_eq!(run("let o={a:1}; delete o.a"), "true");
assert_eq!(run("let o={}; delete o.missing"), "true");
assert_eq!(
run("let o={}; Object.defineProperty(o,'x',{value:1,configurable:false}); delete o.x"),
"false"
);
assert_eq!(
run("let o={}; Object.defineProperty(o,'x',{value:1,configurable:false}); delete o.x; o.x"),
"1"
);
assert_eq!(
run("let o={}; Object.defineProperty(o,'y',{value:2,configurable:true}); delete o.y"),
"true"
);
assert_eq!(run("let o=Object.freeze({a:1}); delete o.a"), "false");
}
#[test]
fn redefine_accessor_as_data() {
assert_eq!(
run(
"let o={}; Object.defineProperty(o,'x',{get(){return 1;},configurable:true}); Object.defineProperty(o,'x',{get(){return 2;},configurable:true}); o.x"
),
"2"
);
assert_eq!(
run(
"let o={}; Object.defineProperty(o,'x',{get(){return 1;},configurable:true}); Object.defineProperty(o,'x',{value:42,configurable:true}); o.x"
),
"42"
);
}
#[test]
fn enumerable_accessor_keys() {
assert_eq!(
run("Object.keys({x:1, get y(){return 2;}}).join(',')"),
"x,y"
);
assert_eq!(
run("JSON.stringify({a:1, get b(){return 2;}})"),
"{\"a\":1,\"b\":2}"
);
assert_eq!(
run(
"let o={a:1}; Object.defineProperty(o,'c',{get(){return 9;},enumerable:true}); Object.keys(o).join(',')"
),
"a,c"
);
assert_eq!(
run(
"let o={a:1}; Object.defineProperty(o,'c',{get(){return 9;}}); Object.keys(o).join(',')"
),
"a"
);
assert_eq!(
run(
"class C{ constructor(){this.a=1;} get b(){return 2;} } Object.keys(new C()).join(',')"
),
"a"
);
}
#[test]
fn get_own_property_descriptors() {
assert_eq!(
run(
"let o={a:1}; Object.defineProperty(o,'b',{value:2,writable:false,enumerable:true}); let d=Object.getOwnPropertyDescriptors(o); d.a.value + ',' + d.a.writable + ',' + d.b.value + ',' + d.b.writable"
),
"1,true,2,false"
);
assert_eq!(
run("Object.keys(Object.getOwnPropertyDescriptors({a:1,b:2})).join(',')"),
"a,b"
);
assert_eq!(
run(
"let o={get x(){return 5;}}; let d=Object.getOwnPropertyDescriptors(o); typeof d.x.get"
),
"function"
);
}
#[test]
fn object_define_properties() {
assert_eq!(
run(
"let o={}; Object.defineProperties(o, { x:{value:1}, y:{get:function(){return 2;}} }); o.x + ',' + o.y"
),
"1,2"
);
assert_eq!(
run("let o={}; Object.defineProperty(o,'a',{value:42}); o.a"),
"42"
);
}
#[test]
fn computed_key_destructuring() {
assert_eq!(
run("let k='name'; let {[k]: v} = {name:'Alice'}; v"),
"Alice"
);
assert_eq!(
run("let p='x'; let {[p]: a, ...rest} = {x:1, y:2}; a + ':' + rest.y"),
"1:2"
);
assert_eq!(
run("let k='m'; let v; ({[k]: v} = {m:42}); String(v)"),
"42"
);
}
#[test]
fn destructuring_assignment_with_defaults() {
assert_eq!(run("let a,b; [a,b]=[1,2]; a+','+b"), "1,2");
assert_eq!(run("let a,b; [a,b]=[1,2]; [a,b]=[b,a]; a+','+b"), "2,1");
assert_eq!(run("let a,b; ({x:a,y:b}={x:10,y:20}); a+','+b"), "10,20");
assert_eq!(run("let a,b,c; [a,b,c=99]=[1,2]; String(c)"), "99");
assert_eq!(run("let x; ({p:x=7}={}); String(x)"), "7");
}
#[test]
fn date_multi_arg_and_subtraction() {
assert_eq!(
run("let d=new Date(2026,5,5); d.getFullYear()+'/'+d.getMonth()+'/'+d.getDate()"),
"2026/5/5"
);
assert_eq!(run("let d=new Date(0); d.getTime()"), "0");
assert_eq!(run("(new Date(2000)) - (new Date(1000))"), "1000");
}
#[test]
fn utf16_string_indexing() {
assert_eq!(run("'café'.length"), "4");
assert_eq!(run("'\\u{1F600}'.length"), "2");
assert_eq!(run("'a\\u{1F600}b'.length"), "4");
assert_eq!(run("'\\u{1F600}'.charCodeAt(0)"), "55357");
assert_eq!(run("'\\u{1F600}'.charCodeAt(1)"), "56832");
assert_eq!(run("'\\u{1F600}'.codePointAt(0)"), "128512");
assert_eq!(run("'a\\u{1F600}b'.codePointAt(1)"), "128512");
assert_eq!(run("'hello'.charCodeAt(0)"), "104");
}
#[test]
fn array_call_and_unary_plus_array() {
assert_eq!(run("Array(3).length"), "3");
assert_eq!(run("Array(1,2,3).join(',')"), "1,2,3");
assert_eq!(run("Array().length"), "0");
assert_eq!(run("+[]"), "0");
assert_eq!(run("+[5]"), "5");
assert_eq!(run("Number.isNaN(+[1,2])"), "true");
assert_eq!(
run("+{[Symbol.toPrimitive](h){ return h==='number'?9:0; }}"),
"9"
);
}
#[test]
fn reverse_inplace_new_array_string_index() {
assert_eq!(
run("let a=[1,2,3]; let b=a.reverse(); (a===b) + ':' + a.join(',')"),
"true:3,2,1"
);
assert_eq!(run("new Array(3).fill(7).join(',')"), "7,7,7");
assert_eq!(run("new Array(1,2,3).join(',')"), "1,2,3");
assert_eq!(run("new Array(3).length"), "3");
assert_eq!(run("'hello'[0] + 'hello'[4]"), "ho");
assert_eq!(run("String('abc'[5])"), "undefined");
}
#[test]
fn array_immutable_methods() {
assert_eq!(
run("let a=[3,1,2]; a.toSorted().join(',') + '|' + a.join(',')"),
"1,2,3|3,1,2"
);
assert_eq!(run("[1,2,3].toReversed().join(',')"), "3,2,1");
assert_eq!(run("[1,2,3].with(1,9).join(',')"), "1,9,3");
assert_eq!(run("[1,2,3].with(-1,9).join(',')"), "1,2,9");
assert_eq!(
run("[1,2,3,4,5].toSpliced(1,2,'a','b').join(',')"),
"1,a,b,4,5"
);
assert_eq!(run("[1,2,3,4].toSpliced(2).join(',')"), "1,2");
assert_eq!(
run("try { [1,2,3].with(10,0); 'no' } catch(e){ e instanceof RangeError }"),
"true"
);
}
#[test]
fn reduce_args_and_sort_in_place() {
assert_eq!(
run(
"let ix=[]; [10,20,30].reduce(function(a,c,i,arr){ ix.push(i + ':' + arr.length); return a+c; }, 0); ix.join(',')"
),
"0:3,1:3,2:3"
);
assert_eq!(
run("['a','b','c'].reduceRight(function(a,c){return a+c;})"),
"cba"
);
assert_eq!(
run("let a=[3,1,2]; let b=a.sort(); (a===b) + ':' + a.join(',')"),
"true:1,2,3"
);
assert_eq!(run("[3,1,2].sort((x,y)=>y-x).join(',')"), "3,2,1");
}
#[cfg(feature = "intl")]
#[test]
fn string_normalize_forms() {
assert_eq!(run("'\u{e9}'.normalize('NFD').length"), "2");
assert_eq!(run("'e\u{301}'.normalize('NFC').length"), "1");
assert_eq!(
run("'\u{e9}'.normalize() === 'e\u{301}'.normalize()"),
"true"
);
assert_eq!(run("'\u{fb01}'.normalize('NFKC')"), "fi");
assert_eq!(run("'abc'.normalize()"), "abc");
assert_eq!(
run("try{'x'.normalize('BAD');'no'}catch(e){e instanceof RangeError}"),
"true"
);
assert_eq!(
run("try{'x'.normalize('BAD');'no'}catch(e){e.name}"),
"RangeError"
);
}
#[test]
fn string_raw_and_member_tag() {
assert_eq!(run("String.raw`a\\nb`"), "a\\nb");
assert_eq!(run("String.raw`${1}+${2}=${3}`"), "1+2=3");
assert_eq!(run("String.raw`line\\tend`.length"), "9"); assert_eq!(
run("let o={ t(s){ return s.raw[0]; } }; o.t`x\\ny`"),
"x\\ny"
);
}
#[test]
fn generator_return_value() {
assert_eq!(
run(
"function* g(){ yield 1; yield 2; return 99; } let it=g(); it.next(); it.next(); let r=it.next(); r.value + ':' + r.done"
),
"99:true"
);
assert_eq!(
run(
"function* g(){ yield 1; return 7; } let it=g(); it.next(); it.next(); String(it.next().value) + ':' + it.next().done"
),
"undefined:true"
);
assert_eq!(
run("function* g(){ yield 1; yield 2; return 9; } [...g()].join(',')"),
"1,2"
);
}
#[test]
fn array_iterators() {
assert_eq!(run("[...['a','b','c'].keys()].join(',')"), "0,1,2");
assert_eq!(run("[...['a','b','c'].values()].join(',')"), "a,b,c");
assert_eq!(
run("let o=[]; for (let [i,v] of ['x','y'].entries()) o.push(i+':'+v); o.join(',')"),
"0:x,1:y"
);
assert_eq!(
run("let it=['p','q'].values(); it.next().value + it.next().value"),
"pq"
);
}
#[test]
fn matchall_replaceall_require_global() {
assert_eq!(
run("try{ 'aaa'.replaceAll(/a/,'b'); 'no' }catch(e){ e instanceof TypeError }"),
"true"
);
assert_eq!(
run("try{ [...'aaa'.matchAll(/a/)]; 'no' }catch(e){ e instanceof TypeError }"),
"true"
);
assert_eq!(run("'aaa'.replaceAll(/a/g,'b')"), "bbb");
assert_eq!(run("[...'a1b2'.matchAll(/[a-z]\\d/g)].length"), "2");
assert_eq!(
run("'2024-06'.replace(/(?<y>\\d+)-(?<m>\\d+)/, '$<m>/$<y>')"),
"06/2024"
);
}
#[test]
fn replace_groups_and_split_limit() {
assert_eq!(
run("'2024-06'.replace(/(?<y>\\d+)-(?<m>\\d+)/, (m,y,mo,o,s,g)=>g.y+'/'+g.m)"),
"2024/06"
);
assert_eq!(run("'a1b2c3'.split(/(\\d)/,3).join('|')"), "a|1|b");
assert_eq!(run("'abc'.split(/(?:)/).join(',')"), "a,b,c");
assert_eq!(run("'a1'.split(/(\\d)/).length"), "3");
}
#[test]
fn regex_unicode_property_categories() {
assert_eq!(run(r#"'Hello World'.match(/\p{Lu}/gu).join('')"#), "HW");
assert_eq!(run(r#"'Hello'.match(/\p{Ll}/gu).join('')"#), "ello");
assert_eq!(run(r#"'abc123'.match(/\p{N}/gu).join('')"#), "123");
assert_eq!(run(r#"'a.b!c'.match(/\p{P}/gu).join('')"#), ".!");
assert_eq!(run(r#"'中文字'.match(/\p{Lo}/gu).length"#), "3");
assert_eq!(run(r#"'a1b2'.match(/\P{N}/gu).join('')"#), "ab");
assert_eq!(
run(r#"'x'.match(/\p{Sm}|\p{Sc}|\p{Mn}|\p{Pd}/gu)===null"#),
"true"
);
}
#[cfg(feature = "intl")]
#[test]
fn regex_unicode_property_precise_with_intl() {
assert_eq!(run(r#"'3+5'.match(/\p{Sm}/u)[0]"#), "+");
assert_eq!(run(r#"'$5'.match(/\p{Sc}/u)[0]"#), "$");
assert_eq!(run(r#"'(a)'.match(/\p{Ps}/u)[0]"#), "(");
assert_eq!(run(r#"'a-b'.match(/\p{Pd}/u)[0]"#), "-");
}
#[test]
fn regex_on_multibyte_strings() {
assert_eq!(run("'café'.match(/é/)[0]"), "é");
assert_eq!(run("'café'.match(/(.+)/)[1]"), "café");
assert_eq!(run("'café'.replace(/é/, 'e')"), "cafe");
assert_eq!(run("'a→b→c'.split(/→/).join('|')"), "a|b|c");
assert_eq!(run("'über 123'.match(/\\d+/)[0]"), "123");
assert_eq!(run("'café'.match(/(?<r>.+)/).groups.r"), "café");
assert_eq!(run("[...'café déjà'.matchAll(/é/g)].length"), "2");
assert_eq!(run("'café'.replace(/f/, '[$&]')"), "ca[f]é");
assert_eq!(run("'café'.replace(/f/, '$`')"), "cacaé");
assert_eq!(run("'café'.replace(/f/, \"$'\")"), "caéé");
assert_eq!(run("'aéb'.replace(/(é)/, '<$1>')"), "a<é>b");
}
#[test]
fn regex_empty_and_zerowidth_matches() {
assert_eq!(run("'abc'.replace(/x*/g, '-')"), "-a-b-c-");
assert_eq!(
run("'camelCaseWord'.split(/(?=[A-Z])/).join('|')"),
"camel|Case|Word"
);
assert_eq!(run("'a1b2'.split(/(\\d)/).join(',')"), "a,1,b,2,");
assert_eq!(
run("'1234567'.replace(/(?<=\\d)(?=(?:\\d{3})+$)/g, ',')"),
"1,234,567"
);
}
#[test]
fn replace_dollar_patterns() {
assert_eq!(run("'hello'.replace('l', '[$&]')"), "he[l]lo");
assert_eq!(run("'abc'.replace('b', '$`')"), "aac"); assert_eq!(run("'abc'.replace('b', \"$'\")"), "acc"); assert_eq!(run("'test'.replaceAll('t', '$$')"), "$es$"); assert_eq!(
run("'2024-06'.replace(/(\\d+)-(\\d+)/, '$2/$1')"),
"06/2024"
);
assert_eq!(run("'x'.replace(/x/, '$1')"), "$1"); assert_eq!(run("'abc'.replace(/b/, '$`')"), "aac");
}
#[test]
fn regex_stateful_last_index() {
assert_eq!(
run(
"let r=/\\d/g; r.exec('a1b2')[0] + ':' + r.lastIndex + ':' + r.exec('a1b2')[0] + ':' + r.lastIndex"
),
"1:2:2:4"
);
assert_eq!(
run("let r=/\\d/g; r.exec('a1'); String(r.exec('a1')) + ':' + r.lastIndex"),
"null:0"
);
assert_eq!(run("let r=/\\d/g; r.lastIndex=3; r.exec('12345')[0]"), "4");
assert_eq!(run("let r=/x/g; r.test('axbx'); r.lastIndex"), "2");
assert_eq!(run("let r=/\\d/; r.exec('a1'); r.lastIndex"), "0");
}
#[test]
fn regex_lookbehind() {
assert_eq!(run("/(?<=\\$)\\d+/.test('$100')"), "true");
assert_eq!(run("/(?<=\\$)\\d+/.test('100')"), "false");
assert_eq!(run("'$100'.match(/(?<=\\$)\\d+/)[0]"), "100");
assert_eq!(
run("'price: $50'.replace(/(?<=\\$)\\d+/, 'X')"),
"price: $X"
);
assert_eq!(run("/(?<!a)b/.test('ab')"), "false");
assert_eq!(run("/(?<!a)b/.test('xb')"), "true");
}
#[test]
fn regex_lookahead_and_backref() {
assert_eq!(run("/foo(?=bar)/.test('foobar')"), "true");
assert_eq!(run("/foo(?=bar)/.test('foobaz')"), "false");
assert_eq!(run("/foo(?!bar)/.test('foobaz')"), "true");
assert_eq!(run("'foobar'.replace(/foo(?=bar)/, 'X')"), "Xbar");
assert_eq!(run("/(\\w)\\1/.test('hello')"), "true");
assert_eq!(run("/(\\w)\\1/.test('abc')"), "false");
assert_eq!(run("'hello'.match(/(.)\\1/)[0]"), "ll");
}
#[test]
fn regex_named_groups() {
assert_eq!(
run("let m='2024-06'.match(/(?<y>\\d{4})-(?<mo>\\d{2})/); m.groups.y + ':' + m.groups.mo"),
"2024:06"
);
assert_eq!(
run("'2024-06'.match(/(?<y>\\d{4})-(?<mo>\\d{2})/)[1]"),
"2024"
);
assert_eq!(
run("'John Smith'.replace(/(?<first>\\w+) (?<last>\\w+)/, '$<last>, $<first>')"),
"Smith, John"
);
assert_eq!(run("String('ab'.match(/(a)(b)/).groups)"), "undefined");
}
#[test]
fn match_all_named_groups() {
assert_eq!(
run(
"let m=[...'2024-06 2025-12'.matchAll(/(?<y>\\d{4})-(?<mo>\\d{2})/g)]; m[0].groups.y + ':' + m[1].groups.mo"
),
"2024:12"
);
assert_eq!(run("[...'a1b2'.matchAll(/([a-z])(\\d)/g)][1][2]"), "2");
assert_eq!(
run("[...'xy'.matchAll(/(?<c>.)/g)].map(m=>m.groups.c).join('')"),
"xy"
);
}
#[test]
fn string_match_all() {
assert_eq!(run("[...'a1b2c3'.matchAll(/([a-z])(\\d)/g)].length"), "3");
assert_eq!(
run("let m=[...'a1b2'.matchAll(/([a-z])(\\d)/g)]; m[0][0] + ':' + m[0][1] + ':' + m[0][2]"),
"a1:a:1"
);
assert_eq!(
run("[...'hello world'.matchAll(/\\w+/g)].map(m=>m[0]).join(',')"),
"hello,world"
);
assert_eq!(run("[...'abc'.matchAll(/\\d/g)].length"), "0");
}
#[test]
fn array_thisarg_and_split_captures() {
assert_eq!(
run("[1,2,3].map(function(x){return x*this.m;},{m:3}).join(',')"),
"3,6,9"
);
assert_eq!(
run("[1,2,3,4].filter(function(x){return x>this.t;},{t:2}).join(',')"),
"3,4"
);
assert_eq!(
run("[1,2,3].some(function(x){return x===this.g;},{g:2})"),
"true"
);
assert_eq!(
run("[1,2,3].every(function(x){return x<=this.mx;},{mx:3})"),
"true"
);
assert_eq!(run("'a1b2c3'.split(/(\\d)/).join('|')"), "a|1|b|2|c|3|");
}
#[test]
fn array_last_index_of_from_index() {
assert_eq!(run("[10,20,30,20,10].lastIndexOf(20)"), "3");
assert_eq!(run("[10,20,30,20,10].lastIndexOf(10,3)"), "0");
assert_eq!(run("[10,20,30,20,10].lastIndexOf(20,-3)"), "1");
assert_eq!(run("[1,2,3].lastIndexOf(9)"), "-1");
assert_eq!(run("[1,2,3,4].findLastIndex(x => x < 3)"), "1");
}
#[test]
fn frozen_object_blocks_delete() {
assert_eq!(
run("let o={a:1,b:2}; Object.freeze(o); delete o.b; o.b"),
"2"
);
assert_eq!(
run("let o={a:1}; Object.freeze(o); o.a=9; o.c=3; o.a + ':' + String(o.c)"),
"1:undefined"
);
assert_eq!(run("let o={a:1,b:2}; delete o.b; String(o.b)"), "undefined");
}
#[test]
fn array_and_function_named_properties() {
assert_eq!(
run("let a=[1,2,3]; a.tag='x'; a.tag + ':' + a.length + ':' + a[0]"),
"x:3:1"
);
assert_eq!(run("let a=[1]; a.tag='y'; a.hasOwnProperty('tag')"), "true");
assert_eq!(run("function f(){} f.meta=42; f.meta"), "42");
assert_eq!(run("function t(s){ return s.raw[0]; } t`a\\tb`"), "a\\tb");
}
#[test]
fn error_to_string() {
assert_eq!(run("new Error('boom').toString()"), "Error: boom");
assert_eq!(run("new TypeError('bad').toString()"), "TypeError: bad");
assert_eq!(run("new Error().toString()"), "Error");
assert_eq!(
run("({ name:'X', message:'y', toString(){ return 'custom'; } }).toString()"),
"custom"
);
}
#[test]
fn symbol_to_primitive_hints() {
let o = "let o={[Symbol.toPrimitive](h){ return h==='number'?42:h==='string'?'str':'def'; }};";
assert_eq!(run(&alloc::format!("{o} +o")), "42");
assert_eq!(run(&alloc::format!("{o} `${{o}}`")), "str");
assert_eq!(run(&alloc::format!("{o} o + ''")), "def");
assert_eq!(
run("let o={[Symbol.toPrimitive](){ return 9; }, valueOf(){ return 1; }}; o + 0"),
"9"
);
}
#[test]
fn loose_equality_object_coercion() {
assert_eq!(run("[] == 0"), "true");
assert_eq!(run("[1] == 1"), "true");
assert_eq!(run("[1,2] == '1,2'"), "true");
assert_eq!(run("({}) == ({})"), "false"); assert_eq!(run("let o={valueOf(){return 5;}}; o == 5"), "true");
assert_eq!(run("'' == 0"), "true");
assert_eq!(run("null == 0"), "false");
}
#[test]
fn to_primitive_in_operators() {
assert_eq!(run("let o={valueOf(){return 42;}}; o + 8"), "50");
assert_eq!(run("let o={valueOf(){return 6;}}; o * 7"), "42");
assert_eq!(run("let o={toString(){return 'x';}}; '' + o"), "x");
assert_eq!(
run("let o={valueOf(){return 5;}, toString(){return 'five';}}; o + 1"),
"6"
);
assert_eq!(run("let o={valueOf(){return 1;}}; o === o"), "true");
}
#[test]
fn template_invokes_custom_tostring() {
assert_eq!(
run("let o = { toString() { return 'custom'; } }; `val=${o}`"),
"val=custom"
);
assert_eq!(run("`${ {a:1} }`"), "[object Object]");
assert_eq!(run("`${[1,2,3]}-${true}-${null}`"), "1,2,3-true-null");
}
#[test]
fn coercion_string_number_join_freeze_tofixed() {
assert_eq!(run("String({toString(){return 'x';}})"), "x");
assert_eq!(run("Number({valueOf(){return 42;}})"), "42");
assert_eq!(
run("[{toString(){return 'a';}},{toString(){return 'b';}}].join(',')"),
"a,b"
);
assert_eq!(
run("let a=[1,2,3]; Object.freeze(a); a.push(4); a.length + ':' + Object.isFrozen(a)"),
"3:true"
);
assert_eq!(run("(0.5).toFixed(0)"), "1");
assert_eq!(run("(2.5).toFixed(0)"), "3");
assert_eq!(run("(123.456).toFixed(2)"), "123.46");
}
#[test]
fn math_abs_round_and_create_descriptors() {
assert_eq!(run("Math.round(-2.5)"), "-2");
assert_eq!(run("Math.round(2.5)"), "3");
assert_eq!(run("Math.round(-0.5) === 0"), "true");
assert_eq!(run("Object.is(Math.abs(-0), 0)"), "true");
assert_eq!(
run(
"let p={g(){return 'hi';}}; let o=Object.create(p, {n:{value:5,enumerable:true}}); o.n + ':' + o.g() + ':' + Object.keys(o).join(',')"
),
"5:hi:n"
);
}
#[test]
fn negative_zero_stringifies_to_zero() {
assert_eq!(run("String(-0)"), "0");
assert_eq!(run("(-0).toString()"), "0");
assert_eq!(run("'' + -0"), "0");
assert_eq!(run("`${-0}`"), "0");
assert_eq!(run("[-0, 0].join(',')"), "0,0");
assert_eq!(run("Object.is(-0, 0)"), "false");
}
#[test]
fn math_minus_zero_indexof_fromindex_number_exponential() {
assert_eq!(run("Object.is(Math.max(-0, 0), 0)"), "true");
assert_eq!(run("Object.is(Math.min(0, -0), -0)"), "true");
assert_eq!(run("'hello world'.indexOf('o', 5)"), "7");
assert_eq!(run("'hello world'.indexOf('o')"), "4");
assert_eq!(run("(1e21).toString()"), "1e+21");
assert_eq!(run("(1e-7).toString()"), "1e-7");
assert_eq!(run("(1e20).toString()"), "100000000000000000000"); assert_eq!(run("(0.000001).toString()"), "0.000001"); }
#[test]
fn math_trig_and_extra() {
assert_eq!(
run("Math.sin(0) + ':' + Math.cos(0) + ':' + Math.tan(0)"),
"0:1:0"
);
assert_eq!(run("Math.round(Math.sin(Math.PI/2))"), "1");
assert_eq!(run("Math.round(Math.atan2(1,1)*4/Math.PI)"), "1");
assert_eq!(
run("Math.cosh(0) + ':' + Math.tanh(0) + ':' + Math.expm1(0)"),
"1:0:0"
);
assert_eq!(
run("Math.fround(1.5) + ':' + (Math.fround(1.1) !== 1.1)"),
"1.5:true"
);
assert_eq!(run("Math.clz32(1) + ':' + Math.clz32(0)"), "31:32");
assert_eq!(run("Math.imul(3,4) + ':' + Math.imul(-1,8)"), "12:-8");
}
#[test]
fn number_formatting() {
assert_eq!(run("(3.5).toString(2)"), "11.1");
assert_eq!(run("(255.5).toString(16)"), "ff.8");
assert_eq!(run("(-255.5).toString(16)"), "-ff.8");
assert_eq!(run("(12345).toPrecision(1)"), "1e+4");
assert_eq!(run("(0.0000001234).toPrecision(2)"), "1.2e-7");
assert_eq!(run("(1234567).toLocaleString()"), "1,234,567");
assert_eq!(run("(-1234.5).toLocaleString()"), "-1,234.5");
}
#[test]
fn math_random_in_range() {
assert_eq!(run("let a=Math.random(); a >= 0 && a < 1"), "true");
assert_eq!(run("Math.random() !== Math.random()"), "true");
assert_eq!(
run("let xs=[]; for (let i=0;i<100;i++) xs.push(Math.random()); xs.every(x=>x>=0&&x<1)"),
"true"
);
}
#[test]
fn math_constants() {
assert_eq!(run("Math.PI > 3.14 && Math.PI < 3.15"), "true");
assert_eq!(run("Math.E > 2.71 && Math.E < 2.72"), "true");
assert_eq!(run("Math.SQRT2 * Math.SQRT2 > 1.999"), "true");
assert_eq!(run("Math.floor(Math.LN2 * 1000)"), "693");
}
#[test]
fn private_in_brand_check() {
assert_eq!(
run(
"class H{ #s=1; static check(o){ return #s in o; } } H.check(new H()) + ':' + H.check({})"
),
"true:false"
);
assert_eq!(
run(
"class H{ #s=1; static check(o){ return #s in o; } } class D extends H{} H.check(new D())"
),
"true"
);
}
#[test]
fn class_static_blocks() {
assert_eq!(
run("class C{ static x=1; static { C.y = C.x + 1; } } C.y"),
"2"
);
assert_eq!(
run("class C{ static n=0; static { C.n=10; } static { C.n+=5; } } C.n"),
"15"
);
assert_eq!(
run("class C{ static x=1; static { this.y = this.x + 100; } } C.y"),
"101"
);
}
#[test]
fn static_setters_and_symbol_description() {
assert_eq!(
run(
"class T{ static _c=0; static get c(){return T._c;} static set c(v){T._c=v;} } T.c=25; T.c"
),
"25"
);
assert_eq!(run("String(Symbol().description)"), "undefined");
assert_eq!(run("Symbol('d').description"), "d");
assert_eq!(run("Symbol('').description"), ""); }
#[test]
fn object_hasown_static_accessors_replaceall_fn() {
assert_eq!(
run("Object.hasOwn({a:1},'a') + ':' + Object.hasOwn({a:1},'b')"),
"true:false"
);
assert_eq!(run("Object.hasOwn(Object.create({x:1}),'x')"), "false");
assert_eq!(
run(
"class C{ static n=0; static inc(){ return ++C.n; } static get cur(){ return C.n; } } C.inc(); C.inc(); C.cur"
),
"2"
);
assert_eq!(
run("'AAA'.replaceAll('A', function(){ return 'B'; })"),
"BBB"
);
assert_eq!(
run("'a1b2'.replace('1', function(m){ return '['+m+']'; })"),
"a[1]b2"
);
}
#[test]
fn object_reflection_and_static_inheritance() {
assert_eq!(run("({a:1}).hasOwnProperty('a')"), "true");
assert_eq!(run("({a:1}).hasOwnProperty('b')"), "false");
assert_eq!(
run("class A { static make(){ return 'made'; } } class B extends A {} B.make()"),
"made"
);
assert_eq!(
run(
"class A { static create(){ return new this(); } get tag(){ return 'a'; } } class B extends A {} B.create().tag"
),
"a"
);
assert_eq!(run("String.raw({ raw: ['a','b','c'] }, 1, 2)"), "a1b2c");
}
#[test]
fn constructor_function_prototype() {
assert_eq!(
run("function A(n){this.n=n;} A.prototype.m=function(){return this.n*2;}; new A(5).m()"),
"10"
);
assert_eq!(
run(
"function A(){} A.prototype.greet=function(){return 'hi';}; function B(){} B.prototype=Object.create(A.prototype); new B().greet()"
),
"hi"
);
assert_eq!(run("function A(){} A.prototype.x=1; A.prototype.x"), "1");
}
#[test]
fn named_function_expression_recurses() {
assert_eq!(
run("let f = function fac(n){ return n <= 1 ? 1 : n * fac(n-1); }; f(5)"),
"120"
);
assert_eq!(
run("let f = function self(n){ return n===0?0:n+self(n-1); }; f(4)"),
"10"
);
}
#[test]
fn array_length_assignment_resizes() {
assert_eq!(run("let a=[1,2,3,4,5]; a.length=3; a.join(',')"), "1,2,3");
assert_eq!(
run("let a=[1,2]; a.length=4; String(a[3]) + ':' + a.length"),
"undefined:4"
);
assert_eq!(run("let a=[1,2,3]; a.length=0; a.length"), "0");
assert_eq!(run("String.fromCodePoint(97, 98, 99)"), "abc");
}
#[test]
fn var_hoisting() {
assert_eq!(
run("function f(){ var a = b; var b = 5; return String(a); } f()"),
"undefined"
);
assert_eq!(
run("function f(){ return typeof later; var later = 1; } f()"),
"undefined"
);
assert_eq!(run("function f(){ { var x = 9; } return x; } f()"), "9");
}
#[test]
fn arrow_inherits_lexical_this() {
assert_eq!(
run("let o = { v: 42, m: function(){ let f = () => this.v; return f(); } }; o.m()"),
"42"
);
assert_eq!(
run("let o = { v: 7, m: function(){ return (() => (() => this.v)())(); } }; o.m()"),
"7"
);
}
#[test]
fn computed_class_members() {
assert_eq!(
run(
"let m='go'; class C{ [m](){return 1;} [m+'V']=2; get [m+'G'](){return 3;} } let c=new C(); c.go() + ':' + c.goV + ':' + c.goG"
),
"1:2:3"
);
assert_eq!(
run(
"let s='mk'; class C{ static [s](){return 'a';} static [s+'N']=4; static get [s+'G'](){return 'b';} } C.mk() + ':' + C.mkN + ':' + C.mkG"
),
"a:4:b"
);
}
#[test]
fn super_member_read() {
assert_eq!(
run(
"class B{ constructor(){this._v=10;} get d(){return this._v*2;} m(){return this._v;} } class D extends B{ get d(){return super.d+1;} m(){return super.m()+5;} } let x=new D(); x.d + ':' + x.m()"
),
"21:15"
);
assert_eq!(
run(
"class A{ greet(){return 'A';} } class C extends A{ greet(){ let f=super.greet; return f.call(this)+'C'; } } new C().greet()"
),
"AC"
);
}
#[test]
fn date_setters_and_parse() {
assert_eq!(
run("let d=new Date(0); d.setUTCFullYear(2000); d.getUTCFullYear()"),
"2000"
);
assert_eq!(
run("let d=new Date(0); d.setUTCMonth(5); d.getUTCMonth()"),
"5"
);
assert_eq!(
run("let d=new Date(0); d.setTime(86400000); d.getUTCDate()"),
"2"
);
assert_eq!(run("Date.parse('1970-01-01T00:00:00.000Z')"), "0");
assert_eq!(
run("Date.parse('2000-01-01T00:00:00.000Z') === Date.UTC(2000,0,1)"),
"true"
);
assert_eq!(
run("new Date('2000-01-01T12:00:00.000Z').getUTCHours()"),
"12"
);
assert_eq!(run("Number.isNaN(Date.parse('garbage'))"), "true");
}
#[test]
fn json_date_and_bigint() {
assert_eq!(
run("JSON.stringify(new Date(0))"),
"\"1970-01-01T00:00:00.000Z\""
);
assert_eq!(
run("JSON.stringify({d:new Date(0)})"),
"{\"d\":\"1970-01-01T00:00:00.000Z\"}"
);
assert_eq!(
run("try{ JSON.stringify(10n); 'no' }catch(e){ e instanceof TypeError }"),
"true"
);
assert_eq!(
run("try{ JSON.stringify({a:1n}); 'no' }catch(e){ e instanceof TypeError }"),
"true"
);
assert_eq!(run("JSON.stringify({a:1,b:'x'})"), "{\"a\":1,\"b\":\"x\"}");
}
#[test]
fn iterator_helpers() {
let g = "function* g(){yield 1;yield 2;yield 3;yield 4;} ";
assert_eq!(
run(&alloc::format!("{g}[...g().map(x=>x*10)].join(',')")),
"10,20,30,40"
);
assert_eq!(
run(&alloc::format!("{g}[...g().filter(x=>x%2===0)].join(',')")),
"2,4"
);
assert_eq!(run(&alloc::format!("{g}[...g().take(2)].join(',')")), "1,2");
assert_eq!(run(&alloc::format!("{g}[...g().drop(2)].join(',')")), "3,4");
assert_eq!(
run(&alloc::format!("{g}g().toArray().join(',')")),
"1,2,3,4"
);
assert_eq!(run(&alloc::format!("{g}g().reduce((a,b)=>a+b,0)")), "10");
assert_eq!(run(&alloc::format!("{g}g().reduce((a,b)=>a+b)")), "10");
assert_eq!(run(&alloc::format!("{g}g().some(x=>x>3)")), "true");
assert_eq!(run(&alloc::format!("{g}g().every(x=>x>2)")), "false");
assert_eq!(run(&alloc::format!("{g}g().find(x=>x>2)")), "3");
assert_eq!(
run(&alloc::format!(
"{g}[...g().map(x=>x*2).filter(x=>x>4)].join(',')"
)),
"6,8"
);
assert_eq!(
run(&alloc::format!(
"{g}let it=g(); it.next(); it.map(x=>x).toArray().join(',')"
)),
"2,3,4"
);
}
#[test]
fn labeled_block_and_class_name() {
assert_eq!(
run("let r=[]; blk:{ r.push(1); if(true)break blk; r.push(2); } r.push(3); r.join(',')"),
"1,3"
);
assert_eq!(run("let h='no'; a:{ b:{ break a; } h='in'; } h"), "no");
assert_eq!(
run(
"let r=[]; outer: for(let i=0;i<3;i++){ for(let j=0;j<3;j++){ if(j===1)continue outer; r.push(i+','+j); } } r.join(';')"
),
"0,0;1,0;2,0"
);
assert_eq!(
run("let C=class Named{ who(){return Named===C;} }; new C().who()"),
"true"
);
assert_eq!(
run("let C=class Named{ n(){return Named.name;} }; new C().n()"),
"Named"
);
assert_eq!(run("class Declared{} Declared.name"), "Declared");
}
#[test]
fn arraybuffer_and_dataview() {
assert_eq!(run("new ArrayBuffer(8).byteLength"), "8");
assert_eq!(
run("let v=new DataView(new ArrayBuffer(8)); v.setInt32(0,42); v.getInt32(0)"),
"42"
);
assert_eq!(
run("let v=new DataView(new ArrayBuffer(8)); v.setInt32(0,-1); v.getUint32(0)"),
"4294967295"
);
assert_eq!(
run("let v=new DataView(new ArrayBuffer(8)); v.setUint8(0,255); v.getInt8(0)"),
"-1"
);
assert_eq!(
run("let v=new DataView(new ArrayBuffer(8)); v.setInt16(0,1000,true); v.getInt16(0,true)"),
"1000"
);
assert_eq!(
run("let v=new DataView(new ArrayBuffer(8)); v.setInt16(0,1000,true); v.getInt16(0,false)"),
"-6141"
);
assert_eq!(
run("let v=new DataView(new ArrayBuffer(8)); v.setFloat64(0,3.14159); v.getFloat64(0)"),
"3.14159"
);
assert_eq!(
run("let v=new DataView(new ArrayBuffer(8)); v.setFloat32(0,1.5); v.getFloat32(0)"),
"1.5"
);
assert_eq!(
run("let v=new DataView(new ArrayBuffer(8)); v.setInt8(0,300); v.getInt8(0)"),
"44"
);
assert_eq!(
run(
"let b=new ArrayBuffer(8); let v=new DataView(b); new DataView(b,2).setInt32(0,7); v.getInt32(2)"
),
"7"
);
}
#[test]
fn typed_arrays() {
assert_eq!(run("new Uint8Array(3).length"), "3");
assert_eq!(run("let a=new Uint8Array(1); a[0]=256; a[0]"), "0");
assert_eq!(run("let a=new Uint8Array(1); a[0]=-1; a[0]"), "255");
assert_eq!(run("let a=new Int8Array(1); a[0]=200; a[0]"), "-56");
assert_eq!(
run("new Uint8ClampedArray([300,-5,100]).join(',')"),
"255,0,100"
);
assert_eq!(run("new Int16Array([70000])[0]"), "4464");
assert_eq!(run("let f=new Float64Array(1); f[0]=3.14; f[0]"), "3.14");
assert_eq!(run("new Uint8Array([1,2,3])[1]"), "2");
assert_eq!(run("new Uint16Array(4).byteLength"), "8");
assert_eq!(run("new Uint8Array(1).BYTES_PER_ELEMENT"), "1");
assert_eq!(run("new Uint8Array([1,2,3]) instanceof Uint8Array"), "true");
assert_eq!(
run("new Uint8Array([1,2,3]).map(x=>x*2).join(',')"),
"2,4,6"
);
assert_eq!(run("[...new Uint8Array([8,9])].join(',')"), "8,9");
}
#[test]
fn bigint_typed_arrays() {
assert_eq!(run("typeof BigInt64Array"), "function");
assert_eq!(run("typeof BigUint64Array"), "function");
assert_eq!(run("BigInt64Array.BYTES_PER_ELEMENT"), "8");
assert_eq!(run("BigUint64Array.BYTES_PER_ELEMENT"), "8");
assert_eq!(run("new BigInt64Array(2).BYTES_PER_ELEMENT"), "8");
assert_eq!(run("BigInt64Array.name"), "BigInt64Array");
assert_eq!(
run("Object.getPrototypeOf(BigInt64Array)===Object.getPrototypeOf(Int8Array)"),
"true"
);
assert_eq!(run("new BigInt64Array(3).length"), "3");
assert_eq!(run("new BigInt64Array(2)[0] === 0n"), "true");
assert_eq!(run("var a=new BigInt64Array([1n,2n]); a[1]===2n"), "true");
assert_eq!(run("new BigInt64Array([-1n])[0]"), "-1");
assert_eq!(
run("new BigUint64Array([18446744073709551615n])[0]"),
"18446744073709551615"
);
assert_eq!(
run("var a=new BigUint64Array(1); a[0]=-1n; a[0]"),
"18446744073709551615"
);
assert_eq!(
run("var a=new BigInt64Array(1); a[0]=18446744073709551617n; a[0]"),
"1"
);
assert_eq!(
run("var a=new BigInt64Array(2); a[0]=true; a[1]='5'; a.join(',')"),
"1,5"
);
assert_eq!(
run("try{var a=new BigInt64Array(1);a[0]=5;'no'}catch(e){e.constructor.name}"),
"TypeError"
);
assert_eq!(
run("try{new BigInt64Array([1,2]);'no'}catch(e){e.constructor.name}"),
"TypeError"
);
assert_eq!(
run("new BigInt64Array([5n,6n,7n]).slice(1).join(',')"),
"6,7"
);
assert_eq!(
run("new BigInt64Array([5n,6n,7n]).subarray(1).join(',')"),
"6,7"
);
assert_eq!(
run("var a=new BigInt64Array(3); a.set([9n,8n],1); a.join(',')"),
"0,9,8"
);
assert_eq!(run("new BigInt64Array(2).fill(7n).join(',')"), "7,7");
assert_eq!(
run("try{new BigInt64Array(2).fill(3);'no'}catch(e){e.constructor.name}"),
"TypeError"
);
assert_eq!(run("BigInt64Array.of(3n,4n).join(',')"), "3,4");
assert_eq!(
run("var s=0n; for(var x of new BigInt64Array([1n,2n,3n])) s+=x; s===6n"),
"true"
);
assert_eq!(
run(
"var a=new BigUint64Array([10n,20n]); var b=new BigUint64Array(a); b[0]===10n && b!==a"
),
"true"
);
assert_eq!(
run(
"var dv=new DataView(new ArrayBuffer(8)); dv.setBigInt64(0,-7n); dv.getBigInt64(0)===-7n"
),
"true"
);
assert_eq!(
run(
"var dv=new DataView(new ArrayBuffer(8)); dv.setBigUint64(0,5n,true); dv.getBigUint64(0,true)===5n"
),
"true"
);
assert_eq!(
run(
"try{new DataView(new ArrayBuffer(8)).setBigInt64(0,5);'no'}catch(e){e.constructor.name}"
),
"TypeError"
);
}
#[test]
fn typed_array_view_aliasing() {
assert_eq!(
run(
"let b=new ArrayBuffer(8); let u=new Uint8Array(b); let f=new Float64Array(b); u[0]=255; f[0]>0"
),
"true"
);
assert_eq!(
run(
"let b=new ArrayBuffer(8); let u=new Uint8Array(b); let dv=new DataView(b); dv.setUint8(1,9); u[1]===9"
),
"true"
);
assert_eq!(
run("let b=new ArrayBuffer(8); let u=new Uint8Array(b,2,4); u[0]=42; new Uint8Array(b)[2]"),
"42"
);
assert_eq!(
run("let u=new Uint8Array([1,2,3,4]); let s=u.subarray(1,3); s[0]=99; u[1]"),
"99"
);
assert_eq!(
run("let u=new Uint8Array(4); u.set([5,6],1); u.join(',')"),
"0,5,6,0"
);
assert_eq!(run("new Uint8Array([1,2,3]).fill(7).join(',')"), "7,7,7");
assert_eq!(
run("new Uint8Array([1,2,3,4]).copyWithin(0,2).join(',')"),
"3,4,3,4"
);
assert_eq!(
run("new Uint8Array(b=new ArrayBuffer(4),2).byteOffset"),
"2"
);
assert_eq!(run("Array.isArray(new Uint8Array([1]))"), "false");
assert_eq!(
run("JSON.stringify(new Uint8Array([1,2,3]))"),
"{\"0\":1,\"1\":2,\"2\":3}"
);
assert_eq!(
run(
"let dv=new DataView(new ArrayBuffer(8)); dv.setBigInt64(0,-1n); dv.getBigInt64(0).toString()"
),
"-1"
);
}
#[test]
fn primitive_wrapper_objects() {
assert_eq!(run("typeof new Number(5)"), "object");
assert_eq!(run("new Number(5).valueOf()"), "5");
assert_eq!(run("new Number(5) + 3"), "8");
assert_eq!(run("new Number(255).toString(16)"), "ff");
assert_eq!(run("new Number(5) instanceof Number"), "true");
assert_eq!(run("new String('hello').length"), "5");
assert_eq!(run("new String('abc')[1]"), "b");
assert_eq!(run("new String('HELLO').toLowerCase()"), "hello");
assert_eq!(run("new String('a') + 'b'"), "ab");
assert_eq!(run("new String('x') instanceof String"), "true");
assert_eq!(run("new Boolean(false).valueOf()"), "false");
assert_eq!(run("new Boolean(false) ? 'truthy' : 'falsy'"), "truthy");
assert_eq!(run("new Boolean(true) instanceof Boolean"), "true");
assert_eq!(run("new Number().valueOf()"), "0");
assert_eq!(run("new String().valueOf()"), "");
}
#[test]
fn sloppy_this_is_global_object() {
assert_eq!(
run("(function(){ function f(){return this===globalThis;} return f(); })()"),
"true"
);
assert_eq!(
run("(function(){ function f(){return typeof this;} return f(); })()"),
"object"
);
assert_eq!(
run("(function(){ function f(){return this===globalThis;} return f.call(null); })()"),
"true"
);
assert_eq!(
run(
"(function(){ var o={m(){ function inner(){return this===globalThis;} return inner(); }}; return o.m(); })()"
),
"true"
);
assert_eq!(
run("(function(){'use strict'; function f(){return this===undefined;} return f(); })()"),
"true"
);
assert_eq!(
run("(function(){'use strict'; function f(){return this;} return f.call(null); })()"),
"null"
);
assert_eq!(
run("(function(){ var o={x:5,m(){return this.x;}}; return o.m(); })()"),
"5"
);
assert_eq!(
run("(function(){ var o={x:9,m(){var a=()=>this.x;return a();}}; return o.m(); })()"),
"9"
);
}
#[test]
fn strict_mode_undeclared_assignment() {
assert_eq!(
run(
"(function(){'use strict'; try{ undeclaredX=1; return 'no'; }catch(e){ return e instanceof ReferenceError ? 'ref' : 'other'; }})()"
),
"ref"
);
assert_eq!(
run("(function(){ sloppyG=5; return typeof sloppyG; })()"),
"number"
);
assert_eq!(
run(
"(function(){'use strict'; return (function(){ try{nx=1;return 'no';}catch(e){return 'ref';} })(); })()"
),
"ref"
);
assert_eq!(
run("(function(){'use strict'; let x=1; x=2; return x; })()"),
"2"
);
assert_eq!(run("'use strict'; var ok='y'; ok"), "y");
assert_eq!(
run(
"(function(){'use strict'; let o={}; Object.defineProperty(o,'x',{value:1,writable:false}); try{o.x=2;return 'no';}catch(e){return e instanceof TypeError?'te':'other';}})()"
),
"te"
);
assert_eq!(
run("let o={}; Object.defineProperty(o,'x',{value:1,writable:false}); o.x=2; o.x"),
"1"
);
assert_eq!(
run(
"(function(){'use strict'; let o=Object.freeze({a:1}); try{o.a=9;return 'no';}catch(e){return e instanceof TypeError?'te':'other';}})()"
),
"te"
);
}
#[test]
fn block_level_function_hoisting() {
assert_eq!(
run("(function(){ {function g(){return 1;}} return typeof g; })()"),
"function"
);
assert_eq!(
run("(function(){ {function g(){return 42;}} return g(); })()"),
"42"
);
assert_eq!(
run("(function(){ if(true){function h(){return 5;}} return h(); })()"),
"5"
);
assert_eq!(
run("(function(){ {{function d(){return 9;}}} return d(); })()"),
"9"
);
assert_eq!(
run("(function(){ function f(){return 'o';} {function f(){return 'i';}} return f(); })()"),
"i"
);
assert_eq!(
run("(function(){ return e(); function e(){return 'h';} })()"),
"h"
);
}
#[test]
fn for_await_of_parses_and_runs() {
assert_eq!(
run(
"async function f(){ let s=0; for await(const x of [1,2,3]) s+=x; return s; } typeof f()"
),
"object"
);
assert_eq!(
run(
"async function* g(){ yield 1; } async function f(){ for await(const x of g()){} } typeof f"
),
"function"
);
assert_eq!(run("let s=0; for(const x of [1,2,3]) s+=x; s"), "6");
}
#[test]
fn intl_number_and_datetime_format() {
assert_eq!(
run("new Intl.NumberFormat('en-US').format(1234.5)"),
"1,234.5"
);
assert_eq!(
run("new Intl.NumberFormat('en-US').format(1000000)"),
"1,000,000"
);
assert_eq!(
run("new Intl.NumberFormat('en-US',{style:'currency',currency:'USD'}).format(1234.5)"),
"$1,234.50"
);
assert_eq!(
run("new Intl.NumberFormat('en-US',{style:'currency',currency:'JPY'}).format(1234)"),
"¥1,234"
);
assert_eq!(
run("new Intl.NumberFormat('en-US',{style:'percent'}).format(0.25)"),
"25%"
);
assert_eq!(
run("new Intl.NumberFormat('en-US',{minimumFractionDigits:2}).format(5)"),
"5.00"
);
assert_eq!(
run("new Intl.NumberFormat('en-US',{useGrouping:false}).format(1234567)"),
"1234567"
);
assert_eq!(
run("new Intl.NumberFormat('en-US').format(-1234.5)"),
"-1,234.5"
);
assert_eq!(run("Intl.NumberFormat('en-US').format(42)"), "42");
assert_eq!(
run("new Intl.DateTimeFormat('en-US').format(new Date(Date.UTC(2020,5,15)))"),
"6/15/2020"
);
}
#[test]
fn date_string_methods() {
assert_eq!(run("new Date(0).toDateString()"), "Thu Jan 01 1970");
assert_eq!(
run("new Date(0).toUTCString()"),
"Thu, 01 Jan 1970 00:00:00 GMT"
);
assert_eq!(
run("new Date(Date.UTC(2020,5,15,10,30,45)).toLocaleString()"),
"6/15/2020, 10:30:45"
);
assert_eq!(
run("new Date(Date.UTC(2020,5,15)).toLocaleDateString()"),
"6/15/2020"
);
assert_eq!(
run("new Date(Date.UTC(2021,11,25)).toDateString()"),
"Sat Dec 25 2021"
);
}
#[test]
fn base64_btoa_atob() {
assert_eq!(run("btoa('hi')"), "aGk=");
assert_eq!(run("btoa('Man')"), "TWFu");
assert_eq!(run("btoa('M')"), "TQ==");
assert_eq!(run("btoa('')"), "");
assert_eq!(run("atob('aGVsbG8=')"), "hello");
assert_eq!(run("atob(btoa('round trip!'))"), "round trip!");
assert_eq!(run("btoa('é')"), "6Q==");
assert_eq!(run("atob('aG k=')"), "hi"); assert_eq!(
run("try{btoa('\\u{1F600}');'no'}catch(e){e instanceof TypeError}"),
"true"
);
}
#[test]
fn structured_clone_deep_copy() {
assert_eq!(
run("let o={b:{c:2}}; let c=structuredClone(o); c.b.c=9; o.b.c"),
"2"
);
assert_eq!(
run("let c=structuredClone([1,[2,3]]); c[1][0]=9; c[1][0]"),
"9"
);
assert_eq!(run("structuredClone(new Map([['k',1]])).get('k')"), "1");
assert_eq!(
run("[...structuredClone(new Set([1,2,3]))].join(',')"),
"1,2,3"
);
assert_eq!(run("structuredClone(new Date(1000)).getTime()"), "1000");
assert_eq!(
run("let o={}; o.self=o; let c=structuredClone(o); c.self===c"),
"true"
);
assert_eq!(
run("let s={v:1}; let c=structuredClone({x:s,y:s}); c.x===c.y"),
"true"
);
assert_eq!(run("structuredClone(42)"), "42");
assert_eq!(
run("try{structuredClone({f:function(){}});'no'}catch(e){e instanceof TypeError}"),
"true"
);
}
#[test]
fn uri_encoding_functions() {
assert_eq!(run("encodeURIComponent('a b&c=d')"), "a%20b%26c%3Dd");
assert_eq!(run("decodeURIComponent('a%20b%26c')"), "a b&c");
assert_eq!(run("encodeURI('http://a.com/x y')"), "http://a.com/x%20y");
assert_eq!(run("encodeURIComponent('café')"), "caf%C3%A9");
assert_eq!(run("decodeURIComponent('caf%C3%A9')"), "café");
assert_eq!(run("encodeURIComponent(\"-_.!~*'()\")"), "-_.!~*'()");
assert_eq!(run("decodeURIComponent('%2f')"), "/");
assert_eq!(
run("try{decodeURIComponent('%zz');'no'}catch(e){e instanceof URIError}"),
"true"
);
assert_eq!(
run("try{decodeURIComponent('%zz');'no'}catch(e){e instanceof Error}"),
"true"
);
}
#[test]
fn global_this_object() {
assert_eq!(run("typeof globalThis"), "object");
assert_eq!(run("globalThis.globalThis === globalThis"), "true");
assert_eq!(run("globalThis.Math.max(1,2,3)"), "3");
assert_eq!(run("globalThis.parseInt('42px')"), "42");
assert_eq!(run("globalThis.Array.isArray([])"), "true");
assert_eq!(run("globalThis.Infinity"), "Infinity");
assert_eq!(run("globalThis.x = 7; globalThis.x"), "7");
}
#[test]
fn map_set_samevaluezero_and_set_ops() {
assert_eq!(run("let m=new Map(); m.set(NaN,'y'); m.get(NaN)"), "y");
assert_eq!(run("new Set([NaN,NaN,1]).size"), "2");
assert_eq!(run("let m=new Map(); m.set(-0,'n'); m.get(0)"), "n");
assert_eq!(
run("[...new Set([1,2,3]).union(new Set([3,4]))].join(',')"),
"1,2,3,4"
);
assert_eq!(
run("[...new Set([1,2,3]).intersection(new Set([2,3,4]))].join(',')"),
"2,3"
);
assert_eq!(
run("[...new Set([1,2,3]).difference(new Set([2]))].join(',')"),
"1,3"
);
assert_eq!(
run("[...new Set([1,2]).symmetricDifference(new Set([2,3]))].join(',')"),
"1,3"
);
assert_eq!(run("new Set([1,2]).isSubsetOf(new Set([1,2,3]))"), "true");
assert_eq!(run("new Set([1,2,3]).isSupersetOf(new Set([1,2]))"), "true");
assert_eq!(run("new Set([1,2]).isDisjointFrom(new Set([3,4]))"), "true");
assert_eq!(
run("try{new Set([1,2,3]).intersection([2,3,9]); 'no'}catch(e){e instanceof TypeError}"),
"true"
);
assert_eq!(
run(
"let sl={size:2,has:v=>v===2||v===3,keys:()=>[2,3][Symbol.iterator]()}; [...new Set([1,2,3]).intersection(sl)].join(',')"
),
"2,3"
);
}
#[test]
fn parse_float_infinity() {
assert_eq!(run("parseFloat('Infinity')"), "Infinity");
assert_eq!(run("parseFloat('-Infinity')"), "-Infinity");
assert_eq!(run("parseFloat(' +Infinity x')"), "Infinity");
assert_eq!(run("parseFloat('InfinityX')"), "Infinity");
assert_eq!(run("Number.isNaN(parseFloat('Inf'))"), "true");
assert_eq!(run("parseFloat('3.14abc')"), "3.14");
}
#[test]
fn define_property_with_symbol_key() {
assert_eq!(
run("let s=Symbol('k'); let o={}; Object.defineProperty(o,s,{value:42}); o[s]"),
"42"
);
assert_eq!(
run(
"let s=Symbol('k'); let o={}; Object.defineProperty(o,s,{value:42}); Object.getOwnPropertyDescriptor(o,s).value"
),
"42"
);
assert_eq!(
run(
"let s=Symbol('k'); let o={}; Object.defineProperty(o,s,{value:42}); Object.getOwnPropertySymbols(o).length"
),
"1"
);
assert_eq!(
run(
"let s=Symbol('a'); let o={}; let v=0; Object.defineProperty(o,s,{get(){return v;},set(n){v=n;}}); o[s]=7; o[s]"
),
"7"
);
assert_eq!(
run(
"let s=Symbol('r'); let o={}; Reflect.defineProperty(o,s,{value:9}); Reflect.getOwnPropertyDescriptor(o,s).value"
),
"9"
);
}
#[test]
fn error_stack_and_aggregate() {
assert_eq!(run("typeof new Error('x').stack"), "string");
assert_eq!(run("new Error('boom').stack.indexOf('boom') >= 0"), "true");
assert_eq!(run("Object.keys(new Error('x')).indexOf('stack')"), "-1");
assert_eq!(
run(
"let a=new AggregateError([new Error('a'),new TypeError('b')],'m'); a.message + ':' + a.errors.length + ':' + a.name"
),
"m:2:AggregateError"
);
assert_eq!(run("new AggregateError([],'x') instanceof Error"), "true");
assert_eq!(
run("new AggregateError(new Set([new Error('x')]),'s').errors.length"),
"1"
);
}
#[test]
fn error_cause_option() {
assert_eq!(run("new Error('m',{cause:'r'}).cause"), "r");
assert_eq!(run("new TypeError('t',{cause:42}).cause"), "42");
assert_eq!(run("String(new Error('m').cause)"), "undefined");
assert_eq!(run("String(new Error('m',{}).cause)"), "undefined");
assert_eq!(
run("new Error('o',{cause:new Error('i')}).cause.message"),
"i"
);
}
#[test]
fn class_extends_native_error() {
assert_eq!(
run(
"class E extends Error{ constructor(m,c){ super(m); this.name='E'; this.c=c; } } let e=new E('x',5); e.message + ':' + e.c + ':' + e.name"
),
"x:5:E"
);
assert_eq!(
run("class E extends Error{} (new E('m')) instanceof Error"),
"true"
);
assert_eq!(
run(
"class E extends Error{ constructor(m){super(m);} } let e=new E('m'); (e instanceof Error) + ',' + (e instanceof E) + ',' + (e instanceof TypeError)"
),
"true,true,false"
);
assert_eq!(
run(
"class V extends RangeError{} let v=new V(); (v instanceof RangeError) + ',' + (v instanceof Error)"
),
"true,true"
);
}
#[test]
fn class_field_init_order_and_computed_fields() {
assert_eq!(
run("class A{ #b; constructor(v){ this.#b=v; } get b(){ return this.#b; } } new A(100).b"),
"100"
);
assert_eq!(
run(
"class A{ #b; constructor(v){ this.#b=v; } add(n){ this.#b+=n; return this.#b; } } let a=new A(100); a.add(50)"
),
"150"
);
assert_eq!(run("let k='x'; class C{ [k+'1']=7; } new C().x1"), "7");
}
#[test]
fn class_rest_params_and_string_positions() {
assert_eq!(
run("class V{constructor(...c){this.c=c;}} new V(...[1,2,3]).c.length"),
"3"
);
assert_eq!(
run("class V{constructor(a, ...r){this.r=r;}} new V(1,2,3).r.join(',')"),
"2,3"
);
assert_eq!(
run("class P{constructor(x=7){this.x=x;}} new P().x + ':' + new P(2).x"),
"7:2"
);
assert_eq!(run("'hello world'.startsWith('world', 6)"), "true");
assert_eq!(run("'hello world'.endsWith('hello', 5)"), "true");
assert_eq!(
run("'hello'.includes('lo', 3) + ':' + 'hello'.includes('he', 1)"),
"true:false"
);
}
#[test]
fn arithmetic_object_coercion() {
assert_eq!(run("[5] - 2"), "3");
assert_eq!(run("[10] / 2"), "5");
assert_eq!(run("[6] & 3"), "2");
assert_eq!(run("[2] ** 3"), "8");
assert_eq!(run("String({} - 1)"), "NaN");
assert_eq!(run("-[5]"), "-5");
assert_eq!(run("new Date(5000) - new Date(2000)"), "3000");
}
#[test]
fn tostring_in_concat_and_property_key() {
assert_eq!(run("'x'.concat({toString(){return 'TS';}})"), "xTS");
assert_eq!(
run("let k={toString(){return 'key';}}; let m={}; m[k]=42; m.key + ':' + m[k]"),
"42:42"
);
}
#[test]
fn relational_object_coercion() {
assert_eq!(run("String([5] < 10)"), "true");
assert_eq!(run("String([20] > 10)"), "true");
assert_eq!(run("String([1] < [2])"), "true"); assert_eq!(run("String([10] < [9])"), "true"); assert_eq!(run("String({} < 1)"), "false"); assert_eq!(run("String(new Date(1) < new Date(2))"), "true"); }
#[test]
fn loose_eq_object_coercion() {
assert_eq!(run("String([] == false)"), "true"); assert_eq!(run("String([] == 0)"), "true");
assert_eq!(run("String([0] == false)"), "true"); assert_eq!(run("String({} == 0)"), "false"); assert_eq!(run("String({} == {})"), "false"); assert_eq!(run("String([1,2] == '1,2')"), "true");
}
#[test]
fn array_string_index_access() {
assert_eq!(run("let a=[10,20,30]; a['0'] + ':' + a['2']"), "10:30");
assert_eq!(run("let a=[10,20,30]; let k='1'; a[k]"), "20");
assert_eq!(
run("let a=[10,20,30]; String(a['00']) + ':' + String(a['01'])"),
"undefined:undefined"
);
assert_eq!(run("[[1,2],[3,4]]['0']['1']"), "2");
}
#[test]
fn object_literal_async_methods_parse() {
assert_eq!(
run("let async=5; let o={async, get:6, set:7}; o.async + ':' + o.get + ':' + o.set"),
"5:6:7"
);
assert_eq!(
run("let o={ async f(){return 1;} }; typeof o.f"),
"function"
);
assert_eq!(
run("let o={ async f(){return 1;} }; typeof o.f()"),
"object"
);
assert_eq!(
run("let k='m'; let o={ async [k](){return 1;} }; typeof o.m"),
"function"
);
}
#[test]
fn object_literal_generator_methods() {
assert_eq!(
run("let o={ *g(){yield 1;yield 2;} }; [...o.g()].join(',')"),
"1,2"
);
assert_eq!(
run("let o={ *[Symbol.iterator](){yield 'a';yield 'b';} }; [...o].join(',')"),
"a,b"
);
assert_eq!(
run("let k='m'; let o={ *[k](){yield 9;} }; [...o.m()].join(',')"),
"9"
);
assert_eq!(
run("let o={ v:5, *items(){yield this.v;yield this.v*2;} }; [...o.items()].join(',')"),
"5,10"
);
}
#[test]
fn class_symbol_iterator_method() {
assert_eq!(
run("class C{ *[Symbol.iterator](){yield 'x';yield 'y';} } [...new C()].join(',')"),
"x,y"
);
assert_eq!(
run(
"class C{ [Symbol.iterator](){let i=0;return{next:()=>i<3?{value:i++,done:false}:{done:true}};} } [...new C()].join(',')"
),
"0,1,2"
);
assert_eq!(
run(
"class C{ *[Symbol.iterator](){yield 1;yield 2;} } let s=0; for(let v of new C())s+=v; s"
),
"3"
);
}
#[test]
fn generator_is_its_own_iterator() {
assert_eq!(
run("function* g(){yield 1;} let it=g(); it[Symbol.iterator]() === it"),
"true"
);
assert_eq!(
run(
"function* g(){yield 1;yield 2;} let it=g(); it[Symbol.iterator]().next().value + ':' + it.next().value"
),
"1:2"
);
assert_eq!(
run("function* g(){yield* [1,2]; yield* 'ab';} [...g()].join(',')"),
"1,2,a,b"
);
}
#[test]
fn explicit_symbol_iterator_call() {
assert_eq!(
run("let it=[10,20,30][Symbol.iterator](); it.next().value + ',' + it.next().value"),
"10,20"
);
assert_eq!(run("'abc'[Symbol.iterator]().next().value"), "a");
assert_eq!(
run("let m=new Map([['k','v']])[Symbol.iterator]().next().value; m[0] + '=' + m[1]"),
"k=v"
);
assert_eq!(run("new Set([1,2])[Symbol.iterator]().next().value"), "1");
assert_eq!(run("[...[1,2,3][Symbol.iterator]()].join(',')"), "1,2,3");
}
#[test]
fn in_operator_walks_prototype_chain() {
assert_eq!(run("'a' in {a:1}"), "true");
assert_eq!(run("'z' in {a:1}"), "false");
assert_eq!(run("let o=Object.create({x:1}); 'x' in o"), "true");
assert_eq!(
run("let o=Object.create(Object.create({deep:1})); 'deep' in o"),
"true"
);
assert_eq!(run("0 in [10,20]"), "true");
assert_eq!(run("5 in [10,20]"), "false");
}
#[test]
fn for_in_inherited_enumeration() {
assert_eq!(
run(
"let p={a:1}; let o=Object.create(p); o.b=2; let k=[]; for(let x in o)k.push(x); k.sort().join(',')"
),
"a,b"
);
assert_eq!(run("let k=[]; for(let x in {})k.push(x); k.length"), "0");
assert_eq!(
run("let o=Object.create({v:1}); o.v=2; let k=[]; for(let x in o)k.push(x); k.length"),
"1"
);
}
#[test]
fn const_reassignment_throws() {
assert_eq!(
run("const x=1; try{ x=2; 'no' }catch(e){ e instanceof TypeError }"),
"true"
);
assert_eq!(run("const x=1; try{ x=2; }catch(e){} x"), "1");
assert_eq!(
run("const n=10; try{ n+=5; 'no' }catch(e){ e instanceof TypeError }"),
"true"
);
assert_eq!(run("const a=[1]; a.push(2); a.length"), "2");
assert_eq!(run("let y=1; y=2; y"), "2");
assert_eq!(run("const a=1; { const a=2; } a"), "1");
}
#[test]
fn destructure_any_iterable() {
assert_eq!(run("let [a,b,c]='xyz'; a+b+c"), "xyz");
assert_eq!(
run("let [f,...r]=new Set([1,2,3,4]); f + ':' + r.join(',')"),
"1:2,3,4"
);
assert_eq!(
run("function* g(){yield 10;yield 20;} let [x,y]=g(); x+y"),
"30"
);
assert_eq!(run("let [[k,v]]=new Map([['a',1]]); k + ':' + v"), "a:1");
}
#[test]
fn computed_member_assignment_eval_order() {
assert_eq!(
run("let a=[0,0]; let i=0; a[i] = i = 1; a[0] + ',' + a[1]"),
"1,0"
);
assert_eq!(run("let a=[1,2,3]; a[1] *= 10; a.join(',')"), "1,20,3");
assert_eq!(run("let o={x:5}; let k='x'; o[k] += 3; o.x"), "8");
assert_eq!(run("let o={set v(n){this._v=n*2;}}; o['v']=10; o._v"), "20");
}
#[test]
fn in_operator_array_bounds_and_delete() {
assert_eq!(run("0 in [1,2,3]"), "true");
assert_eq!(run("5 in [1,2,3]"), "false"); assert_eq!(run("'length' in [1,2,3]"), "true");
assert_eq!(run("'a' in {a:1}"), "true");
assert_eq!(run("'b' in {a:1}"), "false");
assert_eq!(
run("let a=[1,2,3]; delete a[1]; String(a[1]) + ':' + a.length"),
"undefined:3"
);
assert_eq!(run("let o={a:1}; delete o.a; 'a' in o"), "false");
}
#[test]
fn catch_binding_forms() {
assert_eq!(
run("let r; try { throw {code:42, text:'x'}; } catch({code,text}){ r=code+':'+text; } r"),
"42:x"
);
assert_eq!(
run("let r; try { throw [1,2,3]; } catch([a,b]){ r=a+b; } r"),
"3"
);
assert_eq!(
run("let r=false; try { throw 1; } catch { r=true; } r"),
"true"
);
assert_eq!(
run("let r; try { throw new Error('m'); } catch(e){ r=e.message; } r"),
"m"
);
}
#[test]
fn arguments_object() {
assert_eq!(
run(
"function s(){ var t=0; for (var i=0;i<arguments.length;i++) t+=arguments[i]; return t; } s(1,2,3,4)"
),
"10"
);
assert_eq!(
run("function f(){ return arguments[1]; } f('a','b','c')"),
"b"
);
assert_eq!(run("function f(){ return arguments.length; } f()"), "0");
assert_eq!(
run("function outer(){ var a = () => arguments[0]; return a(); } outer('Z')"),
"Z"
);
}
#[test]
fn function_call_apply_bind() {
assert_eq!(
run("function f(p){return p + ':' + this.n;} f.call({n:7}, 'a')"),
"a:7"
);
assert_eq!(
run("function f(a,b){return a+b+this.n;} f.apply({n:1}, [2,3])"),
"6"
);
assert_eq!(
run("function f(a,b){return a+b+this.n;} let g=f.bind({n:10}, 5); g(20) + ':' + typeof g"),
"35:function"
);
assert_eq!(run("Math.max.apply(null, [3,9,2])"), "9");
}
#[test]
fn prototype_chains() {
assert_eq!(
run(
"let p={k:'base',m:function(){return this.n;}}; let o=Object.create(p); o.n=7; o.k + ':' + o.m()"
),
"base:7"
);
assert_eq!(
run("let p={a:1}; let o=Object.create(p); o.b=2; Object.keys(o).join(',')"),
"b"
);
assert_eq!(
run("let p={}; Object.getPrototypeOf(Object.create(p)) === p"),
"true"
);
assert_eq!(
run("Object.getPrototypeOf(Object.create(null)) === null"),
"true"
);
assert_eq!(
run("let a={x:1}; let b=Object.create(a); b.x=2; let c=Object.create(b); c.x"),
"2"
);
assert_eq!(run("let o={}; Object.setPrototypeOf(o,{v:9}); o.v"), "9");
}
#[test]
fn proxy_get_set_traps() {
assert_eq!(
run(
"let t={a:1}; let p=new Proxy(t,{get:function(o,k){return k in o?o[k]:'def';}}); p.a + ':' + p.zzz"
),
"1:def"
);
assert_eq!(
run(
"let t={}; let p=new Proxy(t,{set:function(o,k,v){o[k]=v*3;return true;}}); p.n=4; t.n"
),
"12"
);
assert_eq!(
run("let p=new Proxy({x:5},{}); p.y=6; '' + p.x + p.y"),
"56"
);
assert_eq!(run("typeof new Proxy({}, {})"), "object");
assert_eq!(
run(
"let p=new Proxy({a:1},{has:function(t,k){return k==='magic'||k in t;}}); '' + ('a' in p) + ('magic' in p) + ('z' in p)"
),
"truetruefalse"
);
assert_eq!(
run(
"let seen=''; let p=new Proxy({a:1},{deleteProperty:function(t,k){seen=k; delete t[k]; return true;}}); delete p.a; seen + ':' + ('a' in p)"
),
"a:false"
);
assert_eq!(
run("let p=new Proxy({x:1},{}); let r = 'x' in p; delete p.x; '' + r + ('x' in p)"),
"truefalse"
);
}
#[test]
fn instanceof_array_object_and_fromentries_map() {
assert_eq!(run("[] instanceof Array"), "true");
assert_eq!(run("({}) instanceof Array"), "false");
assert_eq!(run("[] instanceof Object"), "true");
assert_eq!(run("({}) instanceof Object"), "true");
assert_eq!(run("'str' instanceof Object"), "false"); assert_eq!(
run("let m=new Map([['x',10],['y',20]]); let o=Object.fromEntries(m); o.x + ':' + o.y"),
"10:20"
);
}
#[test]
fn collection_foreach_thisarg() {
assert_eq!(
run(
"let r=[]; new Map([['a',1],['b',2]]).forEach(function(v,k){ r.push(k+':'+v*this.m); }, {m:10}); r.join(',')"
),
"a:10,b:20"
);
assert_eq!(
run("let s=0; new Set([1,2,3]).forEach(function(v){ s+=v*this.m; }, {m:2}); s"),
"12"
);
assert_eq!(
run("let n; new Set([1]).forEach((v,k,coll)=>{ n=coll.size; }); n"),
"1"
);
}
#[test]
fn map_set_clear_and_assign_getters() {
assert_eq!(
run("let m=new Map(); m.set('a',1).set('b',2); m.clear(); m.size + ':' + m.has('a')"),
"0:false"
);
assert_eq!(
run("let s=new Set([1,2,3]); s.clear(); s.add(5).add(5); s.size"),
"1"
);
assert_eq!(
run(
"let src={a:1, get b(){ return this.a + 1; }}; let t=Object.assign({}, src); t.a + ',' + t.b"
),
"1,2"
);
assert_eq!(run("Object.assign({}, {x:1}, {y:2}, {x:9}).x"), "9");
}
#[test]
fn bigint_shifts() {
assert_eq!(run("(1n << 8n).toString()"), "256");
assert_eq!(run("(256n >> 2n).toString()"), "64");
assert_eq!(run("(2n ** 32n).toString()"), "4294967296");
assert_eq!(run("(-8n >> 1n).toString()"), "-4");
assert_eq!(run("(-7n >> 1n).toString()"), "-4"); assert_eq!(run("(5n << -1n).toString()"), "2"); }
#[test]
fn number_of_bigint_and_collection_from_iterable() {
assert_eq!(run("Number(100n)"), "100");
assert_eq!(run("Number(-7n)"), "-7");
assert_eq!(run("Number(2n ** 10n)"), "1024");
assert_eq!(run("[...new Set('hello')].join('')"), "helo");
assert_eq!(run("new Set([1,1,2,3]).size"), "3");
assert_eq!(
run("let m=new Map([['a',1],['b',2]]); m.get('a') + m.get('b')"),
"3"
);
}
#[test]
fn weakref_and_finalization_registry() {
assert_eq!(
run("let o={x:1}; let r=new WeakRef(o); (r.deref()===o) + ':' + r.deref().x"),
"true:1"
);
assert_eq!(
run("typeof WeakRef + ',' + typeof FinalizationRegistry"),
"function,function"
);
assert_eq!(
run(
"let reg=new FinalizationRegistry(()=>{}); let t={}; reg.register({}, 'h', t); \
String(reg.unregister(t)) + ',' + String(reg.unregister(t))"
),
"true,false"
);
assert_eq!(
run(
"let reg=new FinalizationRegistry(()=>{}); try { reg.unregister('t'); 'no throw' } \
catch (e) { e.constructor.name }"
),
"TypeError"
);
assert_eq!(
run(
"typeof WeakRef.prototype.deref + ',' + WeakRef.prototype[Symbol.toStringTag] + ',' + \
FinalizationRegistry.prototype[Symbol.toStringTag]"
),
"function,WeakRef,FinalizationRegistry"
);
assert_eq!(run("new WeakRef([1,2,3]).deref().length"), "3");
}
#[test]
fn symbol_prototype_methods() {
assert_eq!(run("typeof Symbol.prototype"), "object");
assert_eq!(run("Symbol.prototype[Symbol.toStringTag]"), "Symbol");
assert_eq!(
run("Symbol.prototype.toString.call(Symbol('x'))"),
"Symbol(x)"
);
assert_eq!(
run("let s=Symbol('y'); Symbol.prototype.valueOf.call(s)===s"),
"true"
);
assert_eq!(
run("typeof Symbol.prototype[Symbol.toPrimitive]"),
"function"
);
assert_eq!(
run("Object.getPrototypeOf(Symbol('66'))===Symbol.prototype"),
"true"
);
assert_eq!(run("Symbol.prototype.constructor===Symbol"), "true");
assert_eq!(
run(
"try { Symbol.prototype.valueOf.call({}); 'no throw' } catch (e) { e.constructor.name }"
),
"TypeError"
);
assert_eq!(run("Symbol('a').description"), "a");
assert_eq!(run("Symbol('a') instanceof Symbol"), "false");
assert_eq!(run("Symbol().hasOwnProperty('description')"), "false");
}
#[test]
fn math_exp_log_reflect_assign_array() {
assert_eq!(run("Math.round(Math.exp(0))"), "1");
assert_eq!(run("Math.round(Math.log(Math.E))"), "1");
assert_eq!(
run("let o=Object.assign({}, ['a','b','c']); o[0] + o[2] + ':' + Object.keys(o).join(',')"),
"ac:0,1,2"
);
assert_eq!(run("Reflect.has(Object.create({k:1}), 'k')"), "true");
assert_eq!(
run("let a=[1,2,3]; Reflect.set(a, 3, 4); a[3] + ':' + a.length"),
"4:4"
);
assert_eq!(
run("Reflect.defineProperty({}, 'x', {value:5}) === true"),
"true"
);
assert_eq!(
run(
"let o={}; Reflect.defineProperty(o,'x',{value:9,enumerable:true}); Reflect.getOwnPropertyDescriptor(o,'x').value"
),
"9"
);
}
#[test]
fn reflect_and_weak_collections() {
assert_eq!(run("let o={a:1}; Reflect.get(o,'a')"), "1");
assert_eq!(run("let o={}; Reflect.set(o,'x',5); o.x"), "5");
assert_eq!(
run("Reflect.has({a:1},'a') + ':' + Reflect.has({a:1},'z')"),
"true:false"
);
assert_eq!(run("Reflect.ownKeys({a:1,b:2,c:3}).length"), "3");
assert_eq!(
run("function f(a,b){return a+b+this.n;} Reflect.apply(f,{n:10},[1,2])"),
"13"
);
assert_eq!(
run("function B(v){this.v=v;} Reflect.construct(B,[9]).v"),
"9"
);
assert_eq!(
run("let k={}; let m=new WeakMap(); m.set(k,'v'); m.get(k) + ':' + m.has(k)"),
"v:true"
);
assert_eq!(run("(new WeakMap()).set({}, 1) instanceof WeakMap"), "true");
assert_eq!(run("(new WeakSet()).add({}) instanceof WeakSet"), "true");
assert_eq!(
run("let s=new WeakSet(); let o={}; s.add(o); s.has(o) + ':' + s.has({})"),
"true:false"
);
}
#[test]
fn proxy_revocable() {
assert_eq!(
run(
"let r=Proxy.revocable({a:1},{get:function(t,k){return t[k];}}); let b=r.proxy.a; r.revoke(); let after='ok'; try { r.proxy.a; } catch(e){ after='threw'; } b + ':' + after"
),
"1:threw"
);
assert_eq!(
run("let r=Proxy.revocable({},{}); typeof r.proxy + ',' + typeof r.revoke"),
"object,function"
);
}
#[test]
fn proxy_apply_construct_traps() {
assert_eq!(
run(
"function f(a,b){return a+b;} let p=new Proxy(f,{apply:function(t,th,a){return a[0]*a[1];}}); p(3,4) + ':' + typeof p"
),
"12:function"
);
assert_eq!(
run("function f(a){return a+1;} let p=new Proxy(f,{}); p(9)"),
"10"
);
assert_eq!(
run(
"function B(v){this.v=v;} let p=new Proxy(B,{construct:function(t,a){return {v:a[0]*2};}}); (new p(5)).v"
),
"10"
);
assert_eq!(
run("function B(v){this.v=v;} let p=new Proxy(B,{}); (new p(7)).v"),
"7"
);
}
#[test]
fn symbols() {
assert_eq!(run("typeof Symbol('x')"), "symbol");
assert_eq!(run("Symbol('hi').toString()"), "Symbol(hi)");
assert_eq!(run("Symbol('hi').description"), "hi");
assert_eq!(run("Symbol('a') === Symbol('a')"), "false");
assert_eq!(run("let s = Symbol(); s === s"), "true");
assert_eq!(run("Symbol.for('k') === Symbol.for('k')"), "true");
assert_eq!(run("Symbol.keyFor(Symbol.for('k2'))"), "k2");
assert_eq!(run("typeof Symbol.iterator"), "symbol");
}
#[test]
fn symbol_keyed_properties() {
assert_eq!(
run(
"let a=Symbol('k'),b=Symbol('k'),o={}; o[a]=1; o[b]=2; o.p=3; '' + o[a] + o[b] + Object.keys(o).join('')"
),
"12p"
);
assert_eq!(run("let s=Symbol(); let o={}; o[s]='v'; s in o"), "true");
assert_eq!(
run("let s=Symbol(); let o={}; o[s]=1; delete o[s]; o[s]"),
"undefined"
);
assert_eq!(
run("let o={}; o[Symbol.iterator]='it'; o[Symbol.iterator]"),
"it"
);
}
#[test]
fn regex_replace_with_function() {
assert_eq!(
run("'a1b2'.replace(/[0-9]/g, function(m){ return '<'+m+'>'; })"),
"a<1>b<2>"
);
assert_eq!(
run("'1-2'.replace(/(\\d)-(\\d)/, function(_, a, b){ return b+'-'+a; })"),
"2-1"
);
assert_eq!(run("'foo'.replace(/o/g, '0')"), "f00");
}
#[test]
fn promise_combinators() {
assert_eq!(
out(
"Promise.all([Promise.resolve(1), 2, Promise.resolve(3)]).then(r => console.log(r.join(',')));"
),
"1,2,3\n"
);
assert_eq!(
out(
"Promise.race([Promise.resolve('a'), Promise.resolve('b')]).then(v => console.log(v));"
),
"a\n"
);
assert_eq!(
out(
"Promise.all([Promise.resolve(1), Promise.reject('boom')]).catch(e => console.log('caught:' + e));"
),
"caught:boom\n"
);
}
#[test]
fn empty_string_is_falsy() {
assert_eq!(run("!!''"), "false");
assert_eq!(run("!!'x'"), "true");
assert_eq!(run("'' || 'fallback'"), "fallback");
assert_eq!(run("if ('') { 'T' } else { 'F' }"), "F");
assert_eq!(run("Boolean('')"), "false");
assert_eq!(
run("[0, '', null, 1, 'a'].filter(function(x){ return x; }).join(',')"),
"1,a"
);
}
#[test]
fn array_from_array_like() {
assert_eq!(
run("Array.from({length:3}, function(_,i){ return i*i; }).join(',')"),
"0,1,4"
);
assert_eq!(run("Array.from({length:2, 0:'a', 1:'b'}).join('-')"), "a-b");
assert_eq!(
run("Array.from([1,2,3], function(x){ return x*2; }).join(',')"),
"2,4,6"
);
}
#[test]
fn array_from_index_and_string_search() {
assert_eq!(run("[1,2,3,2,1].indexOf(2, 2)"), "3");
assert_eq!(run("[1,2,3].includes(2, 2)"), "false");
assert_eq!(run("[5,6,7].indexOf(5, 1)"), "-1");
assert_eq!(run("'hello world'.search('world')"), "6");
assert_eq!(run("'abc'.search('z')"), "-1");
}
#[test]
fn collection_iterators() {
assert_eq!(
run("[...new Map([['a',1],['b',2]]).keys()].join(',')"),
"a,b"
);
assert_eq!(
run("[...new Map([['a',1],['b',2]]).values()].join(',')"),
"1,2"
);
assert_eq!(
run(
"let e=new Map([['a',1],['b',2]]).entries(); let r=e.next(); r.value.join(':')+'/'+r.done"
),
"a:1/false"
);
assert_eq!(
run("[...new Map([['a',1],['b',2]]).entries()].map(p=>p.join(':')).join(',')"),
"a:1,b:2"
);
assert_eq!(run("[...new Set([1,2,3,2]).values()].join(',')"), "1,2,3");
assert_eq!(run("[...new Set([5,6]).keys()].join(',')"), "5,6");
}
#[test]
fn eager_generators() {
assert_eq!(
run(
"function* g(n){ for (let i=0;i<n;i++) yield i*i; } let s=[]; for (let v of g(4)) s.push(v); s.join(',')"
),
"0,1,4,9"
);
assert_eq!(
run("function* g(){ yield 'a'; yield 'b'; } [...g()].join('-')"),
"a-b"
);
assert_eq!(
run(
"function* g(){ yield 1; yield 2; } let it=g(); '' + it.next().value + it.next().value + it.next().done"
),
"12true"
);
assert_eq!(
run(
"function* inner(){ yield 2; yield 3; } function* outer(){ yield 1; yield* inner(); yield 4; } [...outer()].join(',')"
),
"1,2,3,4"
);
}
#[test]
fn new_on_constructor_functions() {
assert_eq!(run("function P(x){ this.x = x; } new P(7).x"), "7");
assert_eq!(
run(
"function P(){} function Q(){} let p = new P(); '' + (p instanceof P) + (p instanceof Q)"
),
"truefalse"
);
assert_eq!(
run("function F(){ this.a = 1; return { b: 2 }; } let o = new F(); '' + o.a + o.b"),
"undefined2"
);
assert_eq!(
run("function P(){ this.v = 1; } Object.keys(new P()).join(',')"),
"v"
);
}
#[test]
fn class_methods_are_non_enumerable() {
assert_eq!(
run(
"class C { m(){ return 1; } constructor(){ this.a = 1; this.b = 2; } } Object.keys(new C()).join(',')"
),
"a,b"
);
assert_eq!(
run(
"class C { greet(){ return 'hi'; } } let c = new C(); c.greet() + ':' + Object.keys({ ...c }).length"
),
"hi:0"
);
}
#[test]
fn optional_calls_destructuring_assign_and_coercion() {
assert_eq!(run("let o = { f: () => 7 }; o.f?.()"), "7");
assert_eq!(run("let o = {}; String(o.missing?.())"), "undefined");
assert_eq!(run("let a = 1, b = 2; [a, b] = [b, a]; a + ',' + b"), "2,1");
assert_eq!(
run("let h, t; [h, ...t] = [1, 2, 3, 4]; h + '|' + t.join(',')"),
"1|2,3,4"
);
assert_eq!(
run("let p = {}; ({ x: p.px, y: p.py } = { x: 10, y: 20 }); p.px + ',' + p.py"),
"10,20"
);
assert_eq!(run("'' + [1, 2, 3]"), "1,2,3");
assert_eq!(run("String([1, 2] + [3, 4])"), "1,23,4");
assert_eq!(run("({}) + '!'"), "[object Object]!");
assert_eq!(
run("try { null.x; } catch (e) { '' + (e instanceof TypeError); }"),
"true"
);
assert_eq!(
run("try { nope; } catch (e) { '' + (e instanceof ReferenceError); }"),
"true"
);
}
#[test]
fn labeled_loops_and_do_while() {
assert_eq!(
run("let count = 0;
outer: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (j === 1) continue outer;
count++;
}
}
count"),
"3"
);
assert_eq!(
run("let hits = 0;
search: for (let i = 0; i < 5; i++) {
for (let j = 0; j < 5; j++) {
hits++;
if (i === 1 && j === 1) break search;
}
}
hits"),
"7"
);
assert_eq!(
run("let n = 0, s = 0; do { s += n; n++; } while (n < 4); s"),
"6"
);
assert_eq!(run("let r = 0; do { r++; } while (false); r"), "1");
}
#[test]
fn for_of_for_in_switch() {
assert_eq!(run("let s = 0; for (const x of [1, 2, 3]) s += x; s"), "6");
assert_eq!(
run("let r = ''; for (const c of 'abc') r += c + '.'; r"),
"a.b.c."
);
assert_eq!(
run("let s = 0; for (const v of new Set([1, 2, 3, 2])) s += v; s"),
"6"
);
assert_eq!(
run("let r = ''; for (const [k, v] of new Map([['a', 1], ['b', 2]])) r += k + v; r"),
"a1b2"
);
assert_eq!(
run("let s = 0; for (const x of [1, 2, 3, 4]) { if (x === 3) break; s += x; } s"),
"3"
);
assert_eq!(
run("let r = ''; for (const k in { a: 1, b: 2 }) r += k; r"),
"ab"
);
assert_eq!(
run("let r = ''; for (const i in ['x', 'y', 'z']) r += i; r"),
"012"
);
assert_eq!(run("let x; let s = 0; for (x of [10, 20]) s += x; s"), "30");
assert_eq!(
run("function f(n) {
let r = '';
switch (n) {
case 1: r += 'one';
case 2: r += 'two'; break;
case 3: r += 'three'; break;
default: r += 'other';
}
return r;
}
f(1) + '|' + f(2) + '|' + f(3) + '|' + f(9)"),
"onetwo|two|three|other"
);
}
#[test]
fn destructuring() {
assert_eq!(run("let [a, b] = [1, 2]; a + b"), "3");
assert_eq!(run("let [a, , c] = [1, 2, 3]; a + c"), "4");
assert_eq!(run("let [a, b = 9] = [1]; a + b"), "10");
assert_eq!(
run("let [first, ...rest] = [1, 2, 3, 4]; rest.join(',')"),
"2,3,4"
);
assert_eq!(run("let { x, y } = { x: 1, y: 2 }; x + y"), "3");
assert_eq!(run("let { a: p, b: q } = { a: 10, b: 20 }; p + q"), "30");
assert_eq!(run("let { m = 7 } = {}; m"), "7");
assert_eq!(
run("let { a, ...others } = { a: 1, b: 2, c: 3 }; Object.keys(others).join(',')"),
"b,c"
);
assert_eq!(run("let { p: { q } } = { p: { q: 42 } }; q"), "42");
assert_eq!(run("let [[a], [b]] = [[1], [2]]; a + b"), "3");
assert_eq!(run("function f([a, b]) { return a * b; } f([3, 4])"), "12");
assert_eq!(
run("function g({ x, y }) { return x + y; } g({ x: 5, y: 6 })"),
"11"
);
assert_eq!(run("function h(a, b = 10) { return a + b; } h(5)"), "15");
assert_eq!(
run("function r(...xs) { return xs.length; } r(1, 2, 3)"),
"3"
);
}
#[test]
fn maps_and_sets() {
assert_eq!(
run("let m = new Map(); m.set('a', 1); m.set('b', 2); m.get('a') + m.get('b')"),
"3"
);
assert_eq!(run("let m = new Map(); m.set('x', 1); m.has('x')"), "true");
assert_eq!(run("let m = new Map(); m.set('x', 1); m.size"), "1");
assert_eq!(
run("let m = new Map(); m.set('x', 1); m.delete('x'); m.has('x')"),
"false"
);
assert_eq!(
run("let m = new Map(); m.set('a', 1); m.set('a', 9); m.get('a') + ':' + m.size"),
"9:1"
);
assert_eq!(
run("let m = new Map([['a', 1], ['b', 2]]); m.get('b')"),
"2"
);
assert_eq!(
run("let s = new Set(); s.add(1); s.add(1); s.add(2); s.size"),
"2"
);
assert_eq!(run("let s = new Set([1, 2, 2, 3]); s.size"), "3");
assert_eq!(run("new Set([1, 2, 3]).has(2)"), "true");
assert_eq!(
run("let m = new Map([['a', 10], ['b', 20]]);
let t = 0; m.forEach(v => { t += v; }); t"),
"30"
);
assert_eq!(run("typeof new Map()"), "object");
}
#[test]
fn more_string_methods() {
assert_eq!(run("'hello world'.slice(0, 5)"), "hello");
assert_eq!(run("'hello'.slice(-3)"), "llo");
assert_eq!(run("'a,b,c'.split(',').join('|')"), "a|b|c");
assert_eq!(run("'hello'.startsWith('he')"), "true");
assert_eq!(run("'hello'.endsWith('lo')"), "true");
assert_eq!(run("'a-b-a'.replace('a', 'X')"), "X-b-a"); assert_eq!(run("'5'.padStart(3, '0')"), "005");
}
#[test]
fn more_array_methods() {
assert_eq!(run("[1, 2, 3, 4].slice(1, 3).join(',')"), "2,3");
assert_eq!(run("[1, 2].concat([3, 4], 5).join(',')"), "1,2,3,4,5");
assert_eq!(run("[1, 2, 3].reverse().join(',')"), "3,2,1");
assert_eq!(run("[1, 2, 3, 4].find(x => x > 2)"), "3");
assert_eq!(run("[1, 2, 3, 4].findIndex(x => x > 2)"), "2");
assert_eq!(run("[1, 2, 3].some(x => x === 2)"), "true");
assert_eq!(run("[1, 2, 3].every(x => x > 0)"), "true");
assert_eq!(run("[1, 2, 3].every(x => x > 1)"), "false");
assert_eq!(run("[3, 1, 2].sort().join(',')"), "1,2,3");
assert_eq!(run("[10, 9, 100].sort().join(',')"), "10,100,9"); assert_eq!(
run("[10, 9, 100].sort((a, b) => a - b).join(',')"),
"9,10,100"
);
assert_eq!(run("[3, 1, 2].sort((a, b) => b - a).join(',')"), "3,2,1");
}
#[test]
fn higher_order_array_methods() {
assert_eq!(run("[1, 2, 3].map(x => x * 2).join(',')"), "2,4,6");
assert_eq!(
run("[1, 2, 3, 4].filter(x => x % 2 === 0).join(',')"),
"2,4"
);
assert_eq!(run("[1, 2, 3, 4].reduce((a, b) => a + b, 0)"), "10");
assert_eq!(run("[1, 2, 3, 4].reduce((a, b) => a + b)"), "10"); assert_eq!(
run("let total = 0; [10, 20, 30].forEach(x => { total += x; }); total"),
"60"
);
assert_eq!(
run("let k = 3;
[1, 2, 3, 4].filter(x => x > 1).map(x => x * k).reduce((a, b) => a + b, 0)"),
"27"
);
}
fn mem_module_bytes() -> alloc::vec::Vec<u8> {
let mut m = alloc::vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
m.extend([
0x01, 0x0b, 0x02, 0x60, 0x02, 0x7f, 0x7f, 0x00, 0x60, 0x01, 0x7f, 0x01, 0x7f,
]);
m.extend([0x03, 0x03, 0x02, 0x00, 0x01]);
m.extend([0x05, 0x03, 0x01, 0x00, 0x01]);
m.extend([
0x07, 0x16, 0x03, 0x03, b'm', b'e', b'm', 0x02, 0x00, 0x05, b's', b't', b'o', b'r', b'e',
0x00, 0x00, 0x04, b'l', b'o', b'a', b'd', 0x00, 0x01,
]);
m.extend([
0x0a, 0x13, 0x02, 0x09, 0x00, 0x20, 0x00, 0x20, 0x01, 0x36, 0x02, 0x00, 0x0b, 0x07, 0x00, 0x20, 0x00, 0x28, 0x02, 0x00, 0x0b, ]);
m
}
fn mem_grow_module_bytes() -> alloc::vec::Vec<u8> {
let mut m = alloc::vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
m.extend([0x01, 0x06, 0x01, 0x60, 0x01, 0x7f, 0x01, 0x7f]);
m.extend([0x03, 0x02, 0x01, 0x00]);
m.extend([0x05, 0x03, 0x01, 0x00, 0x01]);
m.extend([
0x07, 0x14, 0x02, 0x03, b'm', b'e', b'm', 0x02, 0x00, 0x0a, b'g', b'r', b'o', b'w', b'_', b's', b't', b'o', b'r', b'e', 0x00,
0x00, ]);
m.extend([
0x0a, 0x14, 0x01, 0x12, 0x00, 0x41, 0x01, 0x40, 0x00, 0x1a, 0x41, 0xf0, 0xa2, 0x04, 0x20, 0x00, 0x3a, 0x00, 0x00, 0x3f, 0x00, 0x0b, ]);
m
}
fn js_byte_array(bytes: &[u8]) -> alloc::string::String {
let mut s = alloc::string::String::from("[");
for (i, b) in bytes.iter().enumerate() {
if i != 0 {
s.push(',');
}
s.push_str(&alloc::format!("{b}"));
}
s.push(']');
s
}
fn run_wasm(src: &str) -> alloc::string::String {
let combined = alloc::format!("const MOD = {}; {src}", js_byte_array(&mem_module_bytes()));
let program = Parser::parse_program(&combined).expect("parse");
let mut interp = Interp::new();
let value = interp.run(&program).expect("exec");
interp.realm().to_display_string(value)
}
#[test]
fn wasm_memory_shares_byte_store_with_js() {
assert_eq!(
run_wasm(
"const inst = new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array(MOD)));
const mem = inst.exports.mem;
const u8 = new Uint8Array(mem.buffer);
inst.exports.store(16, 0x41); // wasm writes mem[16] = 65
u8[16];"
),
"65"
);
}
#[test]
fn wasm_reads_js_write_before_call() {
assert_eq!(
run_wasm(
"const inst = new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array(MOD)));
const mem = inst.exports.mem;
const u8 = new Uint8Array(mem.buffer);
u8[20] = 0;
// Store 0x12345678 LE at addr 20 from JS via DataView, then wasm loads it.
const dv = new DataView(mem.buffer);
dv.setInt32(20, 0x12345678, true);
inst.exports.load(20);"
),
"305419896" );
}
#[test]
fn wasm_memory_grow_keeps_same_buffer_object_and_shares() {
assert_eq!(
run_wasm(
"const inst = new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array(MOD)));
const mem = inst.exports.mem;
const before = mem.buffer;
const old = mem.grow(1); // grow by one 64KiB page
const same = (mem.buffer === before);
const u8 = new Uint8Array(mem.buffer);
// Write near the top of the newly-grown region from wasm, read in JS.
inst.exports.store(70000, 0x7e);
[old, mem.buffer.byteLength, same, u8[70000]].join(',');"
),
"1,131072,true,126"
);
}
fn run_wasm_wat(wat: &str, src: &str) -> alloc::string::String {
let bin = crate::wasm_spec::wat_to_binary(wat).expect("compile WAT");
let combined = alloc::format!("const MOD = {}; {src}", js_byte_array(&bin));
let program = Parser::parse_program(&combined).expect("parse");
let mut interp = Interp::new();
let value = interp.run(&program).expect("exec");
interp.realm().to_display_string(value)
}
fn run_wasm_grow(src: &str) -> alloc::string::String {
let combined = alloc::format!(
"const MOD = {}; {src}",
js_byte_array(&mem_grow_module_bytes())
);
let program = Parser::parse_program(&combined).expect("parse");
let mut interp = Interp::new();
let value = interp.run(&program).expect("exec");
interp.realm().to_display_string(value)
}
#[test]
fn wasm_grow_during_call_persists_new_page() {
assert_eq!(
run_wasm_grow(
"const inst = new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array(MOD)));
const mem = inst.exports.mem;
const pages = inst.exports.grow_store(0x5a); // store 90 at 70000
const u8 = new Uint8Array(mem.buffer);
[pages, mem.buffer.byteLength, u8[70000]].join(',');"
),
"2,131072,90"
);
}
#[test]
fn wasm_repeat_calls_reuse_instance_state() {
let wat = "(module
(global $c (mut i32) (i32.const 0))
(func (export \"inc\") (result i32)
(global.set $c (i32.add (global.get $c) (i32.const 1)))
(global.get $c)))";
assert_eq!(
run_wasm_wat(
wat,
"const inst = new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array(MOD)));
const a = inst.exports.inc(); // 1
const b = inst.exports.inc(); // 2
const c = inst.exports.inc(); // 3
[a, b, c].join(',');"
),
"1,2,3"
);
}
#[test]
fn embedder_array_buffer_from_bytes_round_trips() {
let mut interp = Interp::new();
let buf = interp.array_buffer_from_bytes(&[10, 20, 30, 40]);
interp.declare_global("buf", NanBox::handle(buf.to_raw()));
let program = Parser::parse_program(
"const v = new Uint8Array(buf); const sum = v[0]+v[1]+v[2]+v[3]; v[1] = 99; sum",
)
.expect("parse");
let value = interp.run(&program).expect("exec");
assert_eq!(interp.realm().to_display_string(value), "100");
let bytes_h = interp.array_buffer_bytes_handle(buf).expect("bytes");
assert_eq!(interp.realm().bytes_at(bytes_h).unwrap(), &[10, 99, 30, 40]);
}
#[test]
#[allow(unsafe_code)] fn embedder_array_buffer_from_external_is_zero_copy() {
let region: &'static mut [u8] = alloc::vec![0u8; 8].leak();
region[0] = 1;
let ptr = region.as_mut_ptr();
let len = region.len();
let mut interp = Interp::new();
let buf = unsafe { interp.array_buffer_from_external(ptr, len, None) };
interp.declare_global("ext", NanBox::handle(buf.to_raw()));
let program = Parser::parse_program(
"const v = new Uint8Array(ext); const seen = v[0]; v[3] = 222; v[7] = 111; seen",
)
.expect("parse");
let value = interp.run(&program).expect("exec");
assert_eq!(interp.realm().to_display_string(value), "1");
assert_eq!(region[3], 222);
assert_eq!(region[7], 111);
}
#[test]
#[allow(unsafe_code)] fn embedder_typed_array_over_external_buffer() {
let region: &'static mut [u8] = alloc::vec![0u8; 16].leak();
let ptr = region.as_mut_ptr();
let mut interp = Interp::new();
let buf = unsafe { interp.array_buffer_from_external(ptr, 16, None) };
let view = interp.typed_array_over(buf, 8, 0, 2).expect("float64 view");
interp.realm_mut().typed_set(view, 1, NanBox::number(3.5));
let back = interp.realm_mut().typed_get(view, 1).unwrap();
assert_eq!(back.as_number(), Some(3.5));
assert_eq!(®ion[8..16], &3.5f64.to_le_bytes());
}
#[test]
fn typed_array_view_ctor_validates_bounds() {
assert_eq!(
run("try{new Uint32Array(new ArrayBuffer(8),0,100);'no'}catch(e){e instanceof RangeError}"),
"true"
);
assert_eq!(
run("try{new Uint16Array(new ArrayBuffer(8),1);'no'}catch(e){e instanceof RangeError}"),
"true"
);
assert_eq!(
run("try{new Uint8Array(new ArrayBuffer(4),8);'no'}catch(e){e instanceof RangeError}"),
"true"
);
assert_eq!(
run("try{new Uint32Array(new ArrayBuffer(6));'no'}catch(e){e instanceof RangeError}"),
"true"
);
assert_eq!(
run("let v=new Uint16Array(new ArrayBuffer(8),2,3); v.length===3 && v.byteOffset===2"),
"true"
);
}
#[test]
fn dataview_ctor_and_access_validate_bounds() {
assert_eq!(
run("try{new DataView(new ArrayBuffer(8),0,100);'no'}catch(e){e instanceof RangeError}"),
"true"
);
assert_eq!(
run(
"try{new DataView(new ArrayBuffer(8),0,8).getInt32(6);'no'}catch(e){e instanceof RangeError}"
),
"true"
);
assert_eq!(
run("try{new DataView(new ArrayBuffer(8),-1);'no'}catch(e){e instanceof RangeError}"),
"true"
);
assert_eq!(
run("let dv=new DataView(new ArrayBuffer(8)); dv.setInt32(0,0x01020304); dv.getInt32(0)"),
"16909060"
);
}
#[test]
fn typed_set_same_kind_fast_path_and_overlap() {
assert_eq!(
run(
"let a=new Uint8Array([1,2,3,4]); let b=new Uint8Array([9,8]); a.set(b,1); a.join(',')"
),
"1,9,8,4"
);
assert_eq!(
run(
"let buf=new ArrayBuffer(4); let full=new Uint8Array(buf); full.set([1,2,3,4]); \
let dst=new Uint8Array(buf,1,3); let src=new Uint8Array(buf,0,3); dst.set(src); full.join(',')"
),
"1,1,2,3"
);
assert_eq!(
run("try{new Uint8Array(2).set([1,2,3]);'no'}catch(e){e instanceof RangeError}"),
"true"
);
assert_eq!(
run("try{new Uint8Array(4).set([1,2],1e308);'no'}catch(e){e instanceof RangeError}"),
"true"
);
assert_eq!(
run("let a=new Uint8Array(3); a.set(new Float64Array([1.9,2.9,3.9])); a.join(',')"),
"1,2,3"
);
}
#[test]
fn typed_fill_and_copy_within_all_kinds() {
for ctor in [
"Int8Array",
"Uint8Array",
"Uint8ClampedArray",
"Int16Array",
"Uint16Array",
"Int32Array",
"Uint32Array",
"Float32Array",
"Float64Array",
] {
assert_eq!(
run(&alloc::format!(
"let a=new {ctor}(4); a.fill(7,1,3); a.join(',')"
)),
"0,7,7,0",
"fill failed for {ctor}"
);
assert_eq!(
run(&alloc::format!(
"let a=new {ctor}([1,2,3,4]); a.copyWithin(0,2); a.join(',')"
)),
"3,4,3,4",
"copyWithin failed for {ctor}"
);
}
assert_eq!(
run("let a=new Uint8ClampedArray(2); a.fill(300); a.join(',')"),
"255,255"
);
assert_eq!(
run("let a=new Int32Array(5); a.fill(9,-2); a.join(',')"),
"0,0,0,9,9"
);
}
#[test]
fn typed_array_aliasing_sees_bulk_writes() {
assert_eq!(
run(
"let buf=new ArrayBuffer(8); let a=new Uint8Array(buf); let b=new Uint8Array(buf); \
a.fill(5); b.join(',')"
),
"5,5,5,5,5,5,5,5"
);
assert_eq!(
run(
"let buf=new ArrayBuffer(4); let a=new Uint8Array(buf); let b=new Uint8Array(buf); \
a.set([10,20,30,40]); a.copyWithin(0,2); b.join(',')"
),
"30,40,30,40"
);
assert_eq!(
run(
"let buf=new ArrayBuffer(4); let a=new Uint8Array(buf); let dv=new DataView(buf); \
a.fill(0xFF); dv.getUint32(0).toString(16)"
),
"ffffffff"
);
}