fix: 修复数组回调与原始值属性访问

This commit is contained in:
2026-06-17 12:59:11 +08:00
parent 6a99209af8
commit f7834df245
10 changed files with 328 additions and 66 deletions
@@ -12,6 +12,18 @@ fn eval(source: &str) -> Result<(Vm, JsValue), String> {
Ok((vm, result))
}
fn to_str(vm: &Vm, val: JsValue) -> String {
vm.kernel_core()
.string_forge()
.lookup(val.as_string_index())
.unwrap_or_default()
}
fn assert_num_eq(val: JsValue, expected: f64) {
let actual = if val.is_int() { val.as_int() as f64 } else { val.as_double() };
assert_eq!(actual, expected);
}
#[test]
fn array_push_adds_element() {
let (_vm, result) = eval("var a = [1,2]; a.push(3); a").unwrap();
@@ -246,6 +258,66 @@ fn array_sort_default() {
assert_eq!(obj.prop_count(), 3);
}
#[test]
fn array_map_filter_reduce_accept_bytecode_callbacks() {
let (vm, result) = eval("[1,2,3].map(function(x){return x+1}).join(',')").unwrap();
assert_eq!(to_str(&vm, result), "2,3,4");
let (vm, result) = eval("[1,2,3].map(x => x * 2).join(',')").unwrap();
assert_eq!(to_str(&vm, result), "2,4,6");
let (vm, result) = eval("[1,2,3,4].filter(x => x % 2 === 0).join(',')").unwrap();
assert_eq!(to_str(&vm, result), "2,4");
let (_vm, result) = eval("[1,2,3].reduce((a,b)=>a+b,0)").unwrap();
assert_num_eq(result, 6.0);
}
#[test]
fn array_iteration_predicates_accept_bytecode_callbacks() {
let (_vm, result) = eval("[1,2,3].forEach(function(x){return x+1})").unwrap();
assert!(result.is_undefined());
let (_vm, result) = eval("[1,2,3].find(x => x > 1)").unwrap();
assert_num_eq(result, 2.0);
let (_vm, result) = eval("[1,2,3].some(x => x === 2)").unwrap();
assert_eq!(result, JsValue::bool(true));
let (_vm, result) = eval("[1,2,3].every(function(x){return x > 0})").unwrap();
assert_eq!(result, JsValue::bool(true));
}
#[test]
fn array_callback_exceptions_propagate() {
let (vm, result) = eval("try{[1].forEach(()=>{throw new TypeError('boom')})}catch(e){e.message}").unwrap();
assert_eq!(to_str(&vm, result), "boom");
let (vm, result) = eval("try{[1].map(()=>{throw new Error('x')})}catch(e){e.message}").unwrap();
assert_eq!(to_str(&vm, result), "x");
let (vm, result) = eval("try{[1].filter(()=>{throw new Error('f')})}catch(e){e.message}").unwrap();
assert_eq!(to_str(&vm, result), "f");
}
#[test]
fn array_sort_uses_user_comparator() {
let (vm, result) = eval("[3,1,2].sort((a,b)=>a-b).join(',')").unwrap();
assert_eq!(to_str(&vm, result), "1,2,3");
let (vm, result) = eval("[3,1,2].sort((a,b)=>b-a).join(',')").unwrap();
assert_eq!(to_str(&vm, result), "3,2,1");
let (vm, result) = eval("[10,9,1].sort().join(',')").unwrap();
assert_eq!(to_str(&vm, result), "1,10,9");
}
#[test]
fn array_sort_propagates_comparator_exception() {
let (vm, result) = eval("try{[1,2,3].sort(()=>{throw new Error('cmp')})}catch(e){e.message}").unwrap();
assert_eq!(to_str(&vm, result), "cmp");
}
#[test]
fn array_copy_within_copies() {
let (_vm, result) = eval("[1,2,3,4,5].copyWithin(0,3)").unwrap();
@@ -106,3 +106,23 @@ fn boolean_new_to_string() {
let r = eval(&mut vm, "new Boolean(false).toString()").unwrap();
assert_eq!(str_val(&vm, r), "false");
}
#[test]
fn boolean_primitive_to_string_autoboxes() {
let mut vm = Vm::new();
let r = eval(&mut vm, "true.toString()").unwrap();
assert_eq!(str_val(&vm, r), "true");
let r = eval(&mut vm, "false.toString()").unwrap();
assert_eq!(str_val(&vm, r), "false");
}
#[test]
fn boolean_primitive_value_of_autoboxes() {
let mut vm = Vm::new();
let r = eval(&mut vm, "false.valueOf()").unwrap();
assert_eq!(r, JsValue::bool(false));
let r = eval(&mut vm, "(true).constructor === Boolean").unwrap();
assert_eq!(r, JsValue::bool(true));
}
@@ -16,6 +16,11 @@ fn to_str(vm: &Vm, val: JsValue) -> String {
.unwrap_or_default()
}
fn assert_num_eq(val: JsValue, expected: f64) {
let actual = if val.is_int() { val.as_int() as f64 } else { val.as_double() };
assert_eq!(actual, expected);
}
#[test]
fn string_index_of_found() {
let mut vm = Vm::new();
@@ -23,6 +28,13 @@ fn string_index_of_found() {
assert_eq!(result.as_int(), 1);
}
#[test]
fn string_from_char_code_static() {
let mut vm = Vm::new();
let result = eval(&mut vm, "String.fromCharCode(65,66,67)").unwrap();
assert_eq!(to_str(&vm, result), "ABC");
}
#[test]
fn string_index_of_not_found() {
let mut vm = Vm::new();
@@ -86,6 +98,26 @@ fn string_to_upper_case() {
assert_eq!(to_str(&vm, s), "HELLO");
}
#[test]
fn string_primitive_length_autoboxes() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'abc'.length").unwrap();
assert_num_eq(result, 3.0);
let result = eval(&mut vm, "''.length").unwrap();
assert_num_eq(result, 0.0);
let result = eval(&mut vm, "'abc'.length + 'de'.length").unwrap();
assert_num_eq(result, 5.0);
}
#[test]
fn string_length_does_not_break_methods() {
let mut vm = Vm::new();
let s = eval(&mut vm, "'hello'.toUpperCase()").unwrap();
assert_eq!(to_str(&vm, s), "HELLO");
}
#[test]
fn string_to_lower_case() {
let mut vm = Vm::new();
+14
View File
@@ -105,3 +105,17 @@ fn test_to_int32_and_to_uint32() {
assert_eq!(coercion::to_int32(JsValue::float(f64::INFINITY), sf), 0);
assert_eq!(coercion::to_uint32(JsValue::int(-1), sf), 4_294_967_295);
}
#[test]
fn primitive_property_write_throws_clean_type_error() {
for source in ["'abc'.x = 1", "(5).foo = 1", "true.foo = 1"] {
let err = eval(source);
assert!(err.contains("TypeError"), "expected TypeError for {source}, got {err}");
assert!(
!err.contains("IC_SET_PROP on non-object")
&& !err.contains("SET_PROP on non-object")
&& !err.contains("SET_PROP_DYNAMIC on non-object"),
"unexpected internal opcode message for {source}: {err}"
);
}
}