Files
OxideJS-New/crates/oxide_builtins/tests/object_boundary_tests.rs
T

304 lines
10 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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_boxes_primitive() {
// ToObject 装箱:Object.keys(42) 返回装箱对象的自身键(空)。
let (_vm, result) = eval("Object.keys(42).join(',')").unwrap();
assert!(result.is_string() && unsafe { &*result.as_string_ptr() }.as_str() == "");
}
#[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_boxes_primitive() {
let (_vm, result) = eval("Object.getOwnPropertyNames(42).join(',')").unwrap();
assert!(result.is_string(), "Object.getOwnPropertyNames(42) should box the primitive");
}
#[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");
}
#[test]
fn test_integrity_freeze_returns_object() {
let (_vm, result) = eval("var o = {x:1}; Object.freeze(o) === o").unwrap();
assert!(result.is_bool() && result.as_bool(), "freeze should return the object");
}
#[test]
fn test_integrity_freeze_sets_non_extensible() {
let (_vm, result) = eval("var o = {x:1}; Object.freeze(o); Object.isExtensible(o)").unwrap();
assert!(result.is_bool() && !result.as_bool(), "freeze should set non-extensible");
}
#[test]
fn test_integrity_seal_non_extensible() {
let (_vm, result) = eval("var o = {x:1}; Object.seal(o); Object.isExtensible(o)").unwrap();
assert!(result.is_bool() && !result.as_bool(), "seal should set non-extensible");
}
#[test]
fn test_integrity_prevent_extensions() {
let (_vm, result) = eval("var o = {x:1}; Object.preventExtensions(o); Object.isExtensible(o)").unwrap();
assert!(result.is_bool() && !result.as_bool(), "preventExtensions should set non-extensible");
}
#[test]
fn test_is_frozen_non_object_returns_true() {
let (_vm, result) = eval("Object.isFrozen(42)").unwrap();
assert!(result.is_bool() && result.as_bool(), "non-object should be frozen per ES spec");
}
#[test]
fn test_is_sealed_non_object_returns_true() {
let (_vm, result) = eval("Object.isSealed(42)").unwrap();
assert!(result.is_bool() && result.as_bool(), "non-object should be sealed per ES spec");
}
#[test]
fn test_is_extensible_non_object_returns_false() {
let (_vm, result) = eval("Object.isExtensible(42)").unwrap();
assert!(result.is_bool() && !result.as_bool(), "non-object should not be extensible per ES spec");
}
#[test]
fn test_is_extensible_no_args_type_error() {
assert!(eval("Object.isExtensible()").is_err());
}
#[test]
fn test_get_prototype_of_null_throws_type_error() {
assert!(eval("Object.getPrototypeOf(null)").is_err());
}
#[test]
fn test_get_prototype_of_undefined_throws_type_error() {
assert!(eval("Object.getPrototypeOf(undefined)").is_err());
}
#[test]
fn test_get_prototype_of_plain_object() {
let (_vm, result) = eval("var o = {}; Object.getPrototypeOf(o)").unwrap();
assert!(result.is_object() || result.is_null());
}
#[test]
fn test_has_own_own_property() {
let (_vm, result) = eval("var o = {a:1}; Object.hasOwn(o, 'a')").unwrap();
assert!(result.is_bool() && result.as_bool());
}
#[test]
fn test_has_own_inherited_property() {
let (_vm, result) = eval("Object.hasOwn({}, 'toString')").unwrap();
assert!(result.is_bool() && !result.as_bool());
}
#[test]
fn test_define_properties_no_args_type_error() {
assert!(eval("Object.defineProperties()").is_err());
}
#[test]
fn test_define_properties_applies_multiple_props() {
let (_vm, result) = eval("var o = {}; Object.defineProperties(o, {a: {value: 1}, b: {value: 2}}); o.a").unwrap();
assert!(result.is_int() || result.is_double());
}
#[test]
fn test_from_entries_no_args_type_error() {
assert!(eval("Object.fromEntries()").is_err());
}
#[test]
fn test_from_entries_empty_array() {
let (_vm, result) = eval("Object.fromEntries([])").unwrap();
assert!(result.is_object(), "fromEntries([]) should return an empty object");
}
#[test]
fn test_object_is_nan() {
let (_vm, result) = eval("Object.is(NaN, NaN)").unwrap();
assert!(result.is_bool() && result.as_bool());
}
#[test]
fn test_object_is_signed_zero() {
let (_vm, result) = eval("Object.is(0, -0)").unwrap();
assert!(result.is_bool() && !result.as_bool());
}
#[test]
fn test_entries_returns_key_value_pairs() {
let (_vm, result) = eval("var o = {a:1, b:2}; Object.entries(o).length").unwrap();
assert!(result.is_int() || result.is_double());
}
#[test]
fn test_values_returns_array() {
let (_vm, result) = eval("var o = {a:1, b:2}; Object.values(o).length").unwrap();
assert!(result.is_int() || result.is_double());
}
#[test]
fn test_get_own_property_descriptor_no_args_type_error() {
assert!(eval("Object.getOwnPropertyDescriptor()").is_err());
}
#[test]
fn test_get_own_property_descriptor_supports_symbol_keys() {
let (_vm, result) = eval(
"var key = Symbol('key');
var object = {};
Object.defineProperty(object, key, { value: 7, writable: false });
var descriptor = Object.getOwnPropertyDescriptor(object, key);
descriptor.value === 7 && descriptor.writable === false",
)
.unwrap();
assert!(result.is_bool() && result.as_bool());
}
#[test]
fn test_get_own_property_descriptor_keeps_undefined_array_properties() {
let (_vm, result) = eval(
"var array = [];
Object.defineProperty(array, 'named', { value: undefined });
Object.getOwnPropertyDescriptor(array, 'named').value === undefined",
)
.unwrap();
assert!(result.is_bool() && result.as_bool());
}
#[test]
fn test_has_own_property_boxes_primitive_this() {
// ToObject 装箱 this:原始值上 hasOwnProperty 返回 false 而非抛错。
let (_vm, result) = eval("Object.prototype.hasOwnProperty.call(42, 'x')").unwrap();
assert!(result.is_bool() && !result.as_bool());
}
#[test]
fn test_property_is_enumerable_own_property() {
let (_vm, result) = eval("var o = {x: 1}; o.propertyIsEnumerable('x')").unwrap();
assert!(result.is_bool() && result.as_bool());
}
fn eval_str(source: &str) -> String {
let (_vm, result) = eval(source).unwrap();
assert!(result.is_string(), "expected string result, got {result:?}");
unsafe { &*result.as_string_ptr() }.as_str().to_string()
}
#[test]
fn delete_prop_removes_own_property() {
// 删除命名属性后 hasOwnProperty / keys / 读取一致反映删除。
assert_eq!(
eval_str("var o={a:1,b:2}; delete o.a; JSON.stringify([o.hasOwnProperty('a'), Object.keys(o).join(',')])"),
"[false,\"b\"]"
);
// 删除数组元素:length 不变,槽位变 hole。
assert_eq!(
eval_str("var a=[1,2,3]; delete a[1]; JSON.stringify([a.length, a.hasOwnProperty('1'), a[1] === undefined])"),
"[3,false,true]"
);
// 值为 undefined 的元素不是 hole,hasOwnProperty 仍为 true。
assert_eq!(
eval_str("var a=[undefined,1]; JSON.stringify([a.hasOwnProperty('0'), 0 in a])"),
"[true,true]"
);
// 删除数组命名属性:元素区保留。
assert_eq!(
eval_str("var a=[1,2]; a.custom=9; delete a.custom; JSON.stringify([a.hasOwnProperty('custom'), a[0]])"),
"[false,1]"
);
// 不可配置属性返回 false 且保留。
assert_eq!(
eval_str("var o={}; Object.defineProperty(o,'x',{value:1,configurable:false}); JSON.stringify([delete o.x, o.hasOwnProperty('x')])"),
"[false,true]"
);
// 原型链属性删除为 no-op true。
assert_eq!(eval_str("var o={}; JSON.stringify(delete o.hasOwnProperty)"), "true");
// 删除后重新赋值数组元素回到元素区。
assert_eq!(eval_str("var a=[1,2,3]; delete a[1]; a[1]=9; JSON.stringify([a[1], a.length])"), "[9,3]");
}