mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
304 lines
10 KiB
Rust
304 lines
10 KiB
Rust
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]");
|
||
}
|