feat: 新增 to_integer_or_infinity/to_length/same_value_zero 三个 ES 抽象操作辅助函数,修复 objects_keys/create/defineProperty/assign/getOwnPropertyNames 五个方法的 TypeError 处理

This commit is contained in:
2026-06-27 14:42:16 +08:00
parent 888d6e2b58
commit b85df71019
6 changed files with 270 additions and 36 deletions
@@ -0,0 +1,101 @@
use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
fn eval(source: &str) -> Result<(Vm, JsValue), String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
let mut vm = Vm::new();
let result = vm.run(&module)?;
Ok((vm, result))
}
#[test]
fn object_keys_no_args_type_error() {
assert!(eval("Object.keys()").is_err(), "Object.keys() should throw TypeError");
}
#[test]
fn object_keys_non_object_type_error() {
assert!(eval("Object.keys(42)").is_err(), "Object.keys(42) should throw TypeError");
}
#[test]
fn object_create_null_proto() {
let (_vm, result) = eval("var o = Object.create(null); o").unwrap();
assert!(result.is_object());
let obj_ptr = result.as_js_object_ptr();
assert!(!obj_ptr.is_null());
let obj = unsafe { &*obj_ptr };
let proto = obj.proto();
assert!(proto.is_null(), "Object.create(null) should set prototype to null");
}
#[test]
fn object_create_number_proto_type_error() {
assert!(eval("Object.create(42)").is_err(), "Object.create(42) should throw TypeError");
}
#[test]
fn object_create_no_args_type_error() {
assert!(eval("Object.create()").is_err(), "Object.create() should throw TypeError");
}
#[test]
fn object_get_own_property_names_no_args_type_error() {
assert!(
eval("Object.getOwnPropertyNames()").is_err(),
"Object.getOwnPropertyNames() should throw TypeError"
);
}
#[test]
fn object_get_own_property_names_non_object_type_error() {
assert!(
eval("Object.getOwnPropertyNames(42)").is_err(),
"Object.getOwnPropertyNames(42) should throw TypeError"
);
}
#[test]
fn object_keys_valid_object() {
let (_vm, result) = eval("var o = {a:1, b:2}; Object.keys(o).join(',')").unwrap();
assert!(result.is_string(), "Object.keys should return an array");
}
#[test]
fn test_define_property_data_and_accessor_conflict() {
assert!(
eval("Object.defineProperty({}, 'x', {value: 1, get: function() {}})").is_err(),
"data+accessor conflict should throw TypeError"
);
}
#[test]
fn test_define_property_non_object_target() {
assert!(
eval("Object.defineProperty(null, 'x', {value: 1})").is_err(),
"non-object target should throw TypeError"
);
}
#[test]
fn test_define_property_missing_descriptor() {
assert!(
eval("Object.defineProperty({}, 'x')").is_err(),
"missing descriptor should throw TypeError"
);
}
#[test]
fn test_assign_skips_null_source() {
let (_vm, result) = eval("var t = {a:1}; Object.assign(t, null); t.a").unwrap();
assert_eq!(result.as_int(), 1, "Object.assign should skip null sources");
}
#[test]
fn test_assign_merges_two_sources() {
let (_vm, result) = eval("var t = {a:1}; Object.assign(t, {b:2}); t.b").unwrap();
assert!(result.is_int() || result.is_double(), "Object.assign should copy from source");
}
@@ -0,0 +1,82 @@
use oxide_runtime_api::{same_value_zero, to_integer_or_infinity, to_length};
use oxide_types::value::JsValue;
#[test]
fn test_to_integer_or_infinity_nan() {
assert_eq!(to_integer_or_infinity(JsValue::float(f64::NAN)), 0.0);
}
#[test]
fn test_to_integer_or_infinity_zero() {
assert_eq!(to_integer_or_infinity(JsValue::float(0.0)), 0.0);
assert_eq!(to_integer_or_infinity(JsValue::float(-0.0)), 0.0);
}
#[test]
fn test_to_integer_or_infinity_infinity() {
assert_eq!(to_integer_or_infinity(JsValue::float(f64::INFINITY)), f64::INFINITY);
assert_eq!(to_integer_or_infinity(JsValue::float(f64::NEG_INFINITY)), f64::NEG_INFINITY);
}
#[test]
fn test_to_integer_or_infinity_truncation() {
assert_eq!(to_integer_or_infinity(JsValue::float(3.7)), 3.0);
assert_eq!(to_integer_or_infinity(JsValue::float(-3.7)), -3.0);
}
#[test]
fn test_to_integer_or_infinity_int() {
assert_eq!(to_integer_or_infinity(JsValue::int(42)), 42.0);
}
#[test]
fn test_to_length_nan() {
assert_eq!(to_length(JsValue::float(f64::NAN)), 0);
}
#[test]
fn test_to_length_negative() {
assert_eq!(to_length(JsValue::float(-1.0)), 0);
assert_eq!(to_length(JsValue::int(-42)), 0);
}
#[test]
fn test_to_length_positive() {
assert_eq!(to_length(JsValue::float(100.0)), 100);
assert_eq!(to_length(JsValue::int(999)), 999);
}
#[test]
fn test_to_length_overflow_clamp() {
assert_eq!(to_length(JsValue::float(1e100)), 9_007_199_254_740_991);
}
#[test]
fn test_same_value_zero_nan() {
assert!(same_value_zero(JsValue::float(f64::NAN), JsValue::float(f64::NAN)));
}
#[test]
fn test_same_value_zero_positive_and_negative_zero() {
assert!(same_value_zero(JsValue::float(0.0), JsValue::float(-0.0)));
assert!(same_value_zero(JsValue::float(-0.0), JsValue::float(0.0)));
}
#[test]
fn test_same_value_zero_equal_numbers() {
assert!(same_value_zero(JsValue::float(42.0), JsValue::float(42.0)));
assert!(same_value_zero(JsValue::int(42), JsValue::int(42)));
assert!(same_value_zero(JsValue::int(42), JsValue::float(42.0)));
}
#[test]
fn test_same_value_zero_different_numbers() {
assert!(!same_value_zero(JsValue::float(1.0), JsValue::float(2.0)));
}
#[test]
fn test_same_value_zero_null_undefined() {
assert!(same_value_zero(JsValue::null(), JsValue::null()));
assert!(same_value_zero(JsValue::undefined(), JsValue::undefined()));
assert!(!same_value_zero(JsValue::null(), JsValue::undefined()));
}