mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
550 lines
20 KiB
Rust
550 lines
20 KiB
Rust
use std::sync::Arc;
|
||
|
||
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(&Arc::new(module))?;
|
||
Ok((vm, result))
|
||
}
|
||
|
||
#[test]
|
||
fn object_keys_empty() {
|
||
let (_vm, result) = eval("Object.keys({})").unwrap();
|
||
assert!(result.is_object());
|
||
let obj = unsafe { &*result.as_js_object_ptr() };
|
||
assert_eq!(obj.prop_count(), 0);
|
||
}
|
||
|
||
#[test]
|
||
fn object_keys_has_own_property() {
|
||
let (_vm, result) = eval("Object.keys({a:1,b:2})").unwrap();
|
||
let obj = unsafe { &*result.as_js_object_ptr() };
|
||
assert_eq!(obj.prop_count(), 2);
|
||
}
|
||
|
||
#[test]
|
||
fn object_member_native_call_uses_regular_call_path() {
|
||
let (_vm, result) = eval("Object.keys({a:1}).length").unwrap();
|
||
assert_eq!(result.as_int(), 1);
|
||
}
|
||
|
||
#[test]
|
||
fn object_member_call_allows_user_overwrite() {
|
||
let (_vm, result) = eval("Object.keys = function() { return 9; }; Object.keys()").unwrap();
|
||
assert_eq!(result.as_int(), 9);
|
||
}
|
||
|
||
#[test]
|
||
fn missing_object_member_call_is_not_call_native_target_error() {
|
||
let err = match eval("Object.noSuchMethod()") {
|
||
Ok(_) => panic!("missing member call should fail"),
|
||
Err(err) => err,
|
||
};
|
||
assert!(err.contains("CALL target is not callable"), "unexpected error: {err}");
|
||
assert!(!err.contains("CALL_NATIVE target"), "unexpected CALL_NATIVE path: {err}");
|
||
}
|
||
|
||
#[test]
|
||
fn object_create_null_proto() {
|
||
let (_vm, _result) = eval("Object.create(null)").unwrap();
|
||
}
|
||
|
||
#[test]
|
||
fn object_assign_copies() {
|
||
let (_vm, result) = eval("Object.assign({a:1},{b:2})").unwrap();
|
||
assert!(result.is_object());
|
||
}
|
||
|
||
#[test]
|
||
fn object_assign_boxes_primitive_target() {
|
||
let (_vm, result) = eval("var result = Object.assign('a'); result.valueOf()").unwrap();
|
||
assert!(result.is_string());
|
||
let rendered = _vm.lookup_str(result).unwrap_or_default();
|
||
assert_eq!(rendered, "a");
|
||
}
|
||
|
||
#[test]
|
||
fn object_is_uses_same_value_semantics() {
|
||
let (_vm, result) = eval("Object.is(NaN, NaN)").unwrap();
|
||
assert_eq!(result, JsValue::bool(true));
|
||
|
||
let (_vm, result) = eval("Object.is(1, 1)").unwrap();
|
||
assert_eq!(result, JsValue::bool(true));
|
||
|
||
let (_vm, result) = eval("Object.is(1, '1')").unwrap();
|
||
assert_eq!(result, JsValue::bool(false));
|
||
}
|
||
|
||
#[test]
|
||
fn object_get_own_property_descriptor_value() {
|
||
let (_vm, result) = eval("Object.getOwnPropertyDescriptor({a:1},'a')").unwrap();
|
||
assert!(result.is_object());
|
||
}
|
||
|
||
#[test]
|
||
fn object_get_own_property_descriptor_missing() {
|
||
let (_vm, result) = eval("Object.getOwnPropertyDescriptor({},'x')").unwrap();
|
||
assert!(result.is_undefined());
|
||
}
|
||
|
||
#[test]
|
||
fn object_define_property_sets_value() {
|
||
let (_vm, result) = eval("Object.defineProperty({},'x',{value:42})").unwrap();
|
||
assert!(result.is_object());
|
||
}
|
||
|
||
#[test]
|
||
fn object_define_property_data_defaults_non_writable() {
|
||
let (_vm, result) =
|
||
eval("var o={}; Object.defineProperty(o,'x',{value:1}); Object.getOwnPropertyDescriptor(o,'x').writable")
|
||
.unwrap();
|
||
assert_eq!(result, JsValue::bool(false));
|
||
}
|
||
|
||
#[test]
|
||
fn object_define_property_accessor_descriptor_gets() {
|
||
let (_vm, result) = eval(
|
||
"var o={}; Object.defineProperty(o,'x',{get:function(){return 7}, enumerable:true, configurable:true}); o.x",
|
||
)
|
||
.unwrap();
|
||
assert_eq!(result, JsValue::int(7));
|
||
}
|
||
|
||
#[test]
|
||
fn object_define_property_rejects_mixed_descriptor() {
|
||
let err = match eval("var o={}; Object.defineProperty(o,'x',{value:1,get:function(){return 2}})") {
|
||
Ok(_) => panic!("expected mixed descriptor to fail"),
|
||
Err(err) => err,
|
||
};
|
||
assert!(err.contains("TypeError"));
|
||
}
|
||
|
||
#[test]
|
||
fn object_define_property_non_writable_assignment_throws_in_strict() {
|
||
let err = match eval("var o={}; Object.defineProperty(o,'x',{value:1}); (function(){'use strict'; o.x=2})()") {
|
||
Ok(_) => panic!("expected assignment to fail"),
|
||
Err(err) => err,
|
||
};
|
||
assert!(err.contains("TypeError"));
|
||
}
|
||
|
||
#[test]
|
||
fn object_get_own_property_descriptor_accessor_shape() {
|
||
let (_vm, result) =
|
||
eval("var o={}; Object.defineProperty(o,'x',{get:function(){return 1}, configurable:true}); Object.getOwnPropertyDescriptor(o,'x').value").unwrap();
|
||
assert!(result.is_undefined());
|
||
}
|
||
|
||
#[test]
|
||
fn object_get_prototype_of() {
|
||
let (_vm, result) = eval("Object.getPrototypeOf({})").unwrap();
|
||
assert!(result.is_object() || result.is_null());
|
||
}
|
||
|
||
#[test]
|
||
fn object_has_own_true() {
|
||
let (_vm, result) = eval("Object.hasOwn({a:1}, 'a')").unwrap();
|
||
assert_eq!(result, JsValue::bool(true));
|
||
}
|
||
|
||
#[test]
|
||
fn object_has_own_false_for_inherited_property() {
|
||
let (_vm, result) = eval("Object.hasOwn({}, 'toString')").unwrap();
|
||
assert_eq!(result, JsValue::bool(false));
|
||
}
|
||
|
||
#[test]
|
||
fn object_proto_has_own_property_call_works() {
|
||
let (_vm, result) = eval("Object.prototype.hasOwnProperty.call(Object, 'assign')").unwrap();
|
||
assert_eq!(result, JsValue::bool(true));
|
||
}
|
||
|
||
#[test]
|
||
fn object_proto_property_is_enumerable_call_works() {
|
||
let (_vm, result) = eval("Object.prototype.propertyIsEnumerable.call(Object, 'assign')").unwrap();
|
||
// 内置方法按 ES 规范不可枚举。
|
||
assert_eq!(result, JsValue::bool(false));
|
||
}
|
||
|
||
#[test]
|
||
fn object_entries_returns_array() {
|
||
let (_vm, result) = eval("Object.entries({a:1,b:2})").unwrap();
|
||
assert!(result.is_object());
|
||
let obj = unsafe { &*result.as_js_object_ptr() };
|
||
assert_eq!(obj.prop_count(), 2);
|
||
}
|
||
|
||
#[test]
|
||
fn object_values_returns_array() {
|
||
let (_vm, result) = eval("Object.values({a:1,b:2})").unwrap();
|
||
assert!(result.is_object());
|
||
let obj = unsafe { &*result.as_js_object_ptr() };
|
||
assert_eq!(obj.prop_count(), 2);
|
||
}
|
||
|
||
// -- Object.fromEntries --
|
||
|
||
#[test]
|
||
fn from_entries_numeric_key_normalized_to_int_key() {
|
||
// 数字键 entry 走 ToPropertyKey 规范化:fromEntries 产出的 "5" 键与 o[5] 同一键。
|
||
let (_vm, result) = eval("Object.fromEntries([[5,'a']])[5]").unwrap();
|
||
assert!(result.is_string() && unsafe { &*result.as_string_ptr() }.as_str() == "a");
|
||
}
|
||
|
||
#[test]
|
||
fn from_entries_string_index_key_reads_via_numeric_access() {
|
||
// 字符串 "5" 键是规范数组下标,读 o[5] 命中同一整数键。
|
||
let (_vm, result) = eval("Object.fromEntries([['5','a']])[5]").unwrap();
|
||
assert!(result.is_string() && unsafe { &*result.as_string_ptr() }.as_str() == "a");
|
||
}
|
||
|
||
#[test]
|
||
fn from_entries_numeric_key_write_keeps_single_key() {
|
||
// 再写 o[5] 不产生第二个 "5" 键(键唯一)。
|
||
let (_vm, result) =
|
||
eval("var o = Object.fromEntries([[5,'a']]); o[5]='x'; Object.getOwnPropertyNames(o).join(',')").unwrap();
|
||
assert!(result.is_string() && unsafe { &*result.as_string_ptr() }.as_str() == "5");
|
||
}
|
||
|
||
#[test]
|
||
fn from_entries_multiple_pairs_roundtrip() {
|
||
let (_vm, result) = eval("var o = Object.fromEntries([[1,'a'],[2,'b']]); o[1] + o[2]").unwrap();
|
||
assert!(result.is_string() && unsafe { &*result.as_string_ptr() }.as_str() == "ab");
|
||
}
|
||
|
||
// -- Object.keys/values/entries 数组元素区 --
|
||
|
||
#[test]
|
||
fn keys_array_includes_integer_elements() {
|
||
let (_vm, result) = eval("Object.keys([1,2]).join(',')").unwrap();
|
||
let s = unsafe { &*result.as_string_ptr() }.to_owned_string();
|
||
assert_eq!(s, "0,1");
|
||
}
|
||
|
||
#[test]
|
||
fn keys_array_skips_holes() {
|
||
let (_vm, result) = eval("Object.keys([1,,3]).join(',')").unwrap();
|
||
let s = unsafe { &*result.as_string_ptr() }.to_owned_string();
|
||
assert_eq!(s, "0,2");
|
||
}
|
||
|
||
#[test]
|
||
fn entries_array_reads_element_values() {
|
||
let (_vm, result) = eval("var e = Object.entries([1,2]); e[1][1]").unwrap();
|
||
assert!(
|
||
result.is_int() && result.as_int() == 2,
|
||
"second entry value should be 2, got {:?}",
|
||
result
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn keys_array_mixed_named_prop_order() {
|
||
// 整数下标升序在前,命名属性按插入序随后(无重复键)。
|
||
let (_vm, result) = eval("var a = [10]; a.foo = 1; Object.keys(a).join(',')").unwrap();
|
||
let s = unsafe { &*result.as_string_ptr() }.to_owned_string();
|
||
assert_eq!(s, "0,foo");
|
||
}
|
||
|
||
#[test]
|
||
fn keys_array_non_enumerable_element_filtered() {
|
||
// defineProperty 把元素改为不可枚举后 keys 应省略(values/entries 同理)。
|
||
let (_vm, result) =
|
||
eval("var a = [1, 2]; Object.defineProperty(a, '1', {enumerable: false}); Object.keys(a).join(',')").unwrap();
|
||
let s = unsafe { &*result.as_string_ptr() }.to_owned_string();
|
||
assert_eq!(s, "0");
|
||
}
|
||
|
||
// -- Object.assign --
|
||
|
||
#[test]
|
||
fn assign_filters_non_enumerable_source() {
|
||
let (_vm, result) = eval(
|
||
"var src = Object.defineProperty({}, 'x', {value: 1, enumerable: false}); JSON.stringify(Object.assign({}, src))",
|
||
)
|
||
.unwrap();
|
||
let s = unsafe { &*result.as_string_ptr() }.to_owned_string();
|
||
assert_eq!(s, "{}");
|
||
}
|
||
|
||
#[test]
|
||
fn assign_triggers_source_getter() {
|
||
let (_vm, result) = eval("var n = 0; var t = Object.assign({}, {get b(){ n++; return 2 }}); [t.b, n]").unwrap();
|
||
let obj = unsafe { &*result.as_js_object_ptr() };
|
||
assert!(
|
||
obj.get_prop_at(0).is_int() && obj.get_prop_at(0).as_int() == 2,
|
||
"getter value should be copied"
|
||
);
|
||
assert!(
|
||
obj.get_prop_at(1).is_int() && obj.get_prop_at(1).as_int() == 1,
|
||
"getter should run exactly once"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn assign_from_array_source() {
|
||
let (_vm, result) = eval("var t = Object.assign({}, [1, 2]); [t['0'], t['1']]").unwrap();
|
||
let obj = unsafe { &*result.as_js_object_ptr() };
|
||
assert!(obj.get_prop_at(0).is_int() && obj.get_prop_at(0).as_int() == 1);
|
||
assert!(obj.get_prop_at(1).is_int() && obj.get_prop_at(1).as_int() == 2);
|
||
}
|
||
|
||
#[test]
|
||
fn assign_target_setter_receives_target() {
|
||
// 规范 Set(to, key, value, true):target 同名 setter 的 this 是 target。
|
||
let (_vm, result) = eval(
|
||
"var recv = null; var t = {}; Object.defineProperty(t, 'x', {set: function(v){ recv = this; }, configurable: true}); Object.assign(t, {x: 1}); recv === t",
|
||
)
|
||
.unwrap();
|
||
assert!(result.is_bool() && result.as_bool(), "target setter should receive target as this");
|
||
}
|
||
|
||
#[test]
|
||
fn assign_to_frozen_target_throws() {
|
||
let err = match eval("var t = Object.freeze({a: 1}); Object.assign(t, {b: 2});") {
|
||
Ok(_) => panic!("assign to frozen target should fail"),
|
||
Err(err) => err,
|
||
};
|
||
assert!(err.contains("extensible"), "unexpected error: {err}");
|
||
}
|
||
|
||
// -- Symbol 键错位回归(shape 槽位计数含 symbol 键) --
|
||
|
||
// Object.setPrototypeOf:对象目标设置原型并返回原对象;proto 非对象/null 抛 TypeError。
|
||
#[test]
|
||
fn object_set_prototype_of_sets_prototype_and_validates() {
|
||
let (_vm, result) =
|
||
eval("var o = {}; (Object.setPrototypeOf(o, null) === o) && (Object.getPrototypeOf(o) === null)").unwrap();
|
||
assert!(result.is_bool() && result.as_bool());
|
||
|
||
let err = match eval("Object.setPrototypeOf({}, 1)") {
|
||
Ok(_) => panic!("non-object prototype should fail"),
|
||
Err(err) => err,
|
||
};
|
||
assert!(err.contains("TypeError"), "unexpected error: {err}");
|
||
}
|
||
|
||
// Object.setPrototypeOf 负向分路:不可扩展目标、原型环、原始值目标。
|
||
#[test]
|
||
fn object_set_prototype_of_negative_paths() {
|
||
// 不可扩展且新旧原型不同抛 TypeError。
|
||
let err = match eval("var o = {}; Object.preventExtensions(o); Object.setPrototypeOf(o, null);") {
|
||
Ok(_) => panic!("non-extensible target with a different prototype should fail"),
|
||
Err(err) => err,
|
||
};
|
||
assert!(err.contains("TypeError"), "unexpected error: {err}");
|
||
|
||
// 不可扩展但新旧原型相同仍成功,符合规范「SameValue 先于可扩展判定」。
|
||
let (_vm, result) = eval(
|
||
"var o = {}; var p = Object.getPrototypeOf(o); Object.preventExtensions(o); \
|
||
Object.setPrototypeOf(o, p) === o",
|
||
)
|
||
.unwrap();
|
||
assert!(
|
||
result.is_bool() && result.as_bool(),
|
||
"same prototype on non-extensible target should succeed"
|
||
);
|
||
|
||
// 原型环抛 TypeError。
|
||
let err = match eval("var a = {}; var b = {}; Object.setPrototypeOf(a, b); Object.setPrototypeOf(b, a);") {
|
||
Ok(_) => panic!("cyclic prototype should fail"),
|
||
Err(err) => err,
|
||
};
|
||
assert!(err.contains("TypeError"), "unexpected error: {err}");
|
||
|
||
// 原始值目标原样返回,不抛错。
|
||
let (_vm, result) = eval("Object.setPrototypeOf(1, null)").unwrap();
|
||
assert!(result.is_int() && result.as_int() == 1, "primitive target should be returned unchanged");
|
||
}
|
||
|
||
#[test]
|
||
fn entries_values_with_interleaved_symbol_key() {
|
||
let (_vm, result) = eval(
|
||
"var s = Symbol(); var o = {a: 1, [s]: 2, b: 3}; [JSON.stringify(Object.entries(o)), JSON.stringify(Object.values(o))]",
|
||
)
|
||
.unwrap();
|
||
let obj = unsafe { &*result.as_js_object_ptr() };
|
||
let s0 = unsafe { &*obj.get_prop_at(0).as_string_ptr() }.to_owned_string();
|
||
let s1 = unsafe { &*obj.get_prop_at(1).as_string_ptr() }.to_owned_string();
|
||
assert_eq!(s0, "[[\"a\",1],[\"b\",3]]");
|
||
assert_eq!(s1, "[1,3]");
|
||
}
|
||
|
||
// -- Object.getOwnPropertyDescriptors --
|
||
|
||
#[test]
|
||
fn object_get_own_property_descriptors_empty() {
|
||
// 结果对象 proto 为 %Object.prototype%,空对象无自身键。
|
||
let (_vm, result) = eval(
|
||
"(function(){ var d = Object.getOwnPropertyDescriptors({}); \
|
||
return Object.getPrototypeOf(d) === Object.prototype && Object.keys(d).length === 0 })()",
|
||
)
|
||
.unwrap();
|
||
assert!(result.is_bool() && result.as_bool());
|
||
}
|
||
|
||
#[test]
|
||
fn object_get_own_property_descriptors_data_and_accessor() {
|
||
// 数据描述符四字段齐全;访问器描述符有 get 无 value。
|
||
let (_vm, result) = eval(
|
||
"var o = {a: 1}; Object.defineProperty(o, 'g', {get: function(){ return 2 }, configurable: true}); \
|
||
var d = Object.getOwnPropertyDescriptors(o); \
|
||
d.a.value === 1 && d.a.writable === true && d.a.enumerable === true && d.a.configurable === true \
|
||
&& d.g.get !== undefined && d.g.value === undefined",
|
||
)
|
||
.unwrap();
|
||
assert!(result.is_bool() && result.as_bool());
|
||
}
|
||
|
||
#[test]
|
||
fn object_get_own_property_descriptors_integer_keys_ascending() {
|
||
// 整数索引键数值升序在前(10 排在 2 后),非整数串键按插入序随后,Symbol 键末段。
|
||
let (_vm, result) = eval(
|
||
"var o = {10: 'a', 2: 'b', x: 'c'}; var s = Symbol(); o[s] = 1; \
|
||
var d = Object.getOwnPropertyDescriptors(o); \
|
||
Object.getOwnPropertyNames(d).join(',') + '|' + d[s].value",
|
||
)
|
||
.unwrap();
|
||
let s = unsafe { &*result.as_string_ptr() }.to_owned_string();
|
||
assert_eq!(s, "2,10,x|1");
|
||
}
|
||
|
||
#[test]
|
||
fn object_get_own_property_descriptors_primitive_receiver() {
|
||
// 原始值接收者经 ToObject 装箱,装箱 number 无自身属性。
|
||
let (_vm, result) =
|
||
eval("(function(){ var d = Object.getOwnPropertyDescriptors(42); return Object.keys(d).length === 0 })()")
|
||
.unwrap();
|
||
assert!(result.is_bool() && result.as_bool());
|
||
}
|
||
|
||
#[test]
|
||
fn object_get_own_property_descriptors_null_receiver_throws() {
|
||
let err = match eval("Object.getOwnPropertyDescriptors(null)") {
|
||
Ok(_) => panic!("null receiver should throw TypeError"),
|
||
Err(err) => err,
|
||
};
|
||
assert!(err.contains("TypeError"), "unexpected error: {err}");
|
||
}
|
||
|
||
// -- defineProperties / create properties 实参语义 --
|
||
|
||
#[test]
|
||
fn object_define_properties_primitive_props_boxed() {
|
||
// properties 实参经 ToObject 装箱(布尔/数字/空串/Symbol/数字盒),
|
||
// 包装盒均无可枚举自身键:零定义,目标原样返回且自身键集不变。
|
||
for arg in ["false", "-12", "''", "Symbol('s')", "42"] {
|
||
let (_vm, result) = eval(&format!(
|
||
"(function(){{ var t = {{m: 1}}; return Object.defineProperties(t, {arg}) === t }})()"
|
||
))
|
||
.unwrap();
|
||
assert!(result.is_bool() && result.as_bool(), "arg {arg}");
|
||
let (_vm, result) = eval(&format!(
|
||
"(function(){{ var t = {{m: 1}}; Object.defineProperties(t, {arg}); return Object.keys(t).length }})()"
|
||
))
|
||
.unwrap();
|
||
assert_eq!(result.as_int(), 1, "arg {arg}");
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn object_create_primitive_props_boxed() {
|
||
// Object.create 的 properties 实参同走 ToObject:数字盒零键,仅原型生效。
|
||
let (_vm, result) = eval(
|
||
"(function(){ var p = {}; var o = Object.create(p, 42); \
|
||
return Object.getPrototypeOf(o) === p && Object.keys(o).length === 0 })()",
|
||
)
|
||
.unwrap();
|
||
assert!(result.is_bool() && result.as_bool());
|
||
}
|
||
|
||
#[test]
|
||
fn object_create_bigint_props_boxed() {
|
||
// BigInt 实参装箱,BigInt 盒零可枚举自身键:仅原型生效。
|
||
let (_vm, result) = eval(
|
||
"(function(){ var p = {}; var o = Object.create(p, 0n); \
|
||
return Object.getPrototypeOf(o) === p && Object.keys(o).length === 0 })()",
|
||
)
|
||
.unwrap();
|
||
assert!(result.is_bool() && result.as_bool());
|
||
}
|
||
|
||
#[test]
|
||
fn object_define_properties_null_props_throws() {
|
||
// null 是 ToObject 拒绝的值:两入口均抛 TypeError。
|
||
let err = match eval("Object.defineProperties({}, null)") {
|
||
Ok(_) => panic!("null properties should throw TypeError"),
|
||
Err(err) => err,
|
||
};
|
||
assert!(err.contains("TypeError"), "unexpected error: {err}");
|
||
let err = match eval("Object.create(null, null)") {
|
||
Ok(_) => panic!("null properties should throw TypeError"),
|
||
Err(err) => err,
|
||
};
|
||
assert!(err.contains("TypeError"), "unexpected error: {err}");
|
||
}
|
||
|
||
#[test]
|
||
fn object_define_property_primitive_target_throws() {
|
||
// target 不做 ToObject 装箱:原始值直接抛 TypeError。
|
||
for src in [
|
||
"Object.defineProperty(0, 'a', {value: 1})",
|
||
"Object.defineProperty(true, 'a', {value: 1})",
|
||
"Object.defineProperty('abc', 'a', {value: 1})",
|
||
] {
|
||
let err = match eval(src) {
|
||
Ok(_) => panic!("{src} should throw TypeError"),
|
||
Err(err) => err,
|
||
};
|
||
assert!(err.contains("TypeError"), "unexpected error: {err}");
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn object_define_properties_primitive_target_throws() {
|
||
// target 不做 ToObject 装箱:原始值直接抛 TypeError。
|
||
for src in [
|
||
"Object.defineProperties(0, {})",
|
||
"Object.defineProperties(true, {})",
|
||
"Object.defineProperties('abc', {})",
|
||
] {
|
||
let err = match eval(src) {
|
||
Ok(_) => panic!("{src} should throw TypeError"),
|
||
Err(err) => err,
|
||
};
|
||
assert!(err.contains("TypeError"), "unexpected error: {err}");
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn object_create_getter_range_error_propagates() {
|
||
// 描述符收集期访问器 getter 抛出的用户异常原类型穿透,不投影为 TypeError。
|
||
let err = match eval("Object.create(null, {get p(){ throw new RangeError('boom'); }})") {
|
||
Ok(_) => panic!("getter RangeError should propagate"),
|
||
Err(err) => err,
|
||
};
|
||
assert!(err.contains("RangeError"), "unexpected error: {err}");
|
||
}
|
||
|
||
#[test]
|
||
fn object_define_properties_two_phase_get_before_define() {
|
||
// 两阶段语义:全部描述符 getter 先触发后逐键定义;后键 getter 抛出时
|
||
// 前键尚未定义。
|
||
let (_vm, result) = eval(
|
||
"(function(){ var t = {}; var n = 0; \
|
||
Object.defineProperty(t, 'p1', { get: function(){ n++; return {value: 1}; }, enumerable: true }); \
|
||
Object.defineProperty(t, 'p2', { get: function(){ if (n === 1) { n++; throw 42; } return {value: 2}; }, enumerable: true }); \
|
||
var target = {}; \
|
||
try { Object.defineProperties(target, t); } catch (e) {} \
|
||
return n === 2 && target.hasOwnProperty('p1') === false && target.hasOwnProperty('p2') === false })()",
|
||
)
|
||
.unwrap();
|
||
assert!(result.is_bool() && result.as_bool());
|
||
}
|