diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index f576d03..3126be3 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -287,10 +287,13 @@ pub fn object_define_property(vm: &mut H, args: &[u8]) -> NativeResul } } else { if existing_pos.is_none() { - return NativeResult::Err(crate::error::create_type_error( - vm, - "Cannot define new property without value or accessor", - )); + if vm + .define_data_property(obj, si, JsValue::undefined(), PropAttributes::new(false, enumerable, configurable)) + .is_err() + { + return NativeResult::Err(crate::error::create_type_error(vm, "Cannot define property")); + } + return NativeResult::Ok(obj_val); } let is_accessor = existing_meta.map(|m| m.is_accessor).unwrap_or(false); let writable = existing_meta.map(|m| m.attributes.writable()).unwrap_or(true); diff --git a/crates/oxide_vm/tests/define_property_descriptor_tests.rs b/crates/oxide_vm/tests/define_property_descriptor_tests.rs index 7548245..bd53e7b 100644 --- a/crates/oxide_vm/tests/define_property_descriptor_tests.rs +++ b/crates/oxide_vm/tests/define_property_descriptor_tests.rs @@ -92,11 +92,11 @@ fn get_own_property_descriptor_accessor_fields() { ); } -// -- New property without value or accessor throws -- +// -- New property without value or accessor defaults to data descriptor (ES spec behavior) -- #[test] -fn define_property_new_without_value_throws() { - let result = eval_str("var obj={}; Object.defineProperty(obj,'x',{enumerable:true})"); - assert_err_contains(result, "Cannot define new property without value or accessor"); +fn define_property_new_attributes_only_defaults_value_undefined() { + let r = eval_str("var obj={}; Object.defineProperty(obj,'x',{enumerable:true}); obj.x").unwrap(); + assert!(r.is_undefined(), "new prop with only enumerable should default value to undefined, got {:?}", r); } // -- Partial configurable on existing property preserves value --