diff --git a/crates/oxide_vm/src/builtins/date.rs b/crates/oxide_vm/src/builtins/date.rs index 70b3ee8..03f4747 100644 --- a/crates/oxide_vm/src/builtins/date.rs +++ b/crates/oxide_vm/src/builtins/date.rs @@ -76,6 +76,41 @@ pub fn date_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { let timestamp = if args.len() < 2 { Utc::now().timestamp_millis() as f64 + } else if args.len() > 2 { + let y = coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as i32; + let m = coercion::to_number(vm.reg(args[2]), vm.kernel().string_forge().as_ref()) as u32; + let d = if args.len() > 3 { + coercion::to_number(vm.reg(args[3]), vm.kernel().string_forge().as_ref()) as u32 + } else { + 1 + }; + let h = if args.len() > 4 { + coercion::to_number(vm.reg(args[4]), vm.kernel().string_forge().as_ref()) as u32 + } else { + 0 + }; + let min = if args.len() > 5 { + coercion::to_number(vm.reg(args[5]), vm.kernel().string_forge().as_ref()) as u32 + } else { + 0 + }; + let sec = if args.len() > 6 { + coercion::to_number(vm.reg(args[6]), vm.kernel().string_forge().as_ref()) as u32 + } else { + 0 + }; + let ms = if args.len() > 7 { + coercion::to_number(vm.reg(args[7]), vm.kernel().string_forge().as_ref()) as u32 + } else { + 0 + }; + NaiveDate::from_ymd_opt(y, m + 1, d) + .and_then(|nd| { + nd.and_hms_milli_opt(h, min, sec, ms) + .and_then(|ndt| ndt.and_local_timezone(Utc).earliest()) + }) + .map(|dt| dt.timestamp_millis() as f64) + .unwrap_or(f64::NAN) } else { let val = vm.reg(args[1]); if val.is_string() { @@ -120,45 +155,7 @@ pub fn date_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { let obj = unsafe { &*val.as_js_object_ptr() }; get_timestamp(obj) } else { - let y = - coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as i32; - let m = if args.len() > 2 { - coercion::to_number(vm.reg(args[2]), vm.kernel().string_forge().as_ref()) as u32 - } else { - 0 - }; - let d = if args.len() > 3 { - coercion::to_number(vm.reg(args[3]), vm.kernel().string_forge().as_ref()) as u32 - } else { - 1 - }; - let h = if args.len() > 4 { - coercion::to_number(vm.reg(args[4]), vm.kernel().string_forge().as_ref()) as u32 - } else { - 0 - }; - let min = if args.len() > 5 { - coercion::to_number(vm.reg(args[5]), vm.kernel().string_forge().as_ref()) as u32 - } else { - 0 - }; - let sec = if args.len() > 6 { - coercion::to_number(vm.reg(args[6]), vm.kernel().string_forge().as_ref()) as u32 - } else { - 0 - }; - let ms = if args.len() > 7 { - coercion::to_number(vm.reg(args[7]), vm.kernel().string_forge().as_ref()) as u32 - } else { - 0 - }; - NaiveDate::from_ymd_opt(y, m + 1, d) - .and_then(|nd| { - nd.and_hms_milli_opt(h, min, sec, ms) - .and_then(|ndt| ndt.and_local_timezone(Utc).earliest()) - }) - .map(|dt| dt.timestamp_millis() as f64) - .unwrap_or(f64::NAN) + f64::NAN } }; diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index ff7a832..7ab48ce 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -717,6 +717,9 @@ fn bind_date(kernel: &Arc, global: &mut JsObject) { let sf = kernel.string_forge().as_ref(); let sh = kernel.shape_forge().as_ref(); + ctor.set_native_fn(Some(crate::builtins::date::date_constructor as *const ())); + ctor.set_native_arg_count(7); + bind_method!( kernel.builtin_world(), ctor, @@ -1053,7 +1056,11 @@ fn bind_date(kernel: &Arc, global: &mut JsObject) { fn bind_set(kernel: &Arc, global: &mut JsObject) { let ctor_ptr = kernel.builtin_world().set_constructor.as_ptr() as *mut JsObject; + let ctor = unsafe { &mut *ctor_ptr }; let proto_ptr = kernel.builtin_world().set_proto.as_ptr() as *mut JsObject; + + ctor.set_native_fn(Some(crate::builtins::set::set_constructor as *const ())); + ctor.set_native_arg_count(1); let proto = unsafe { &mut *proto_ptr }; let sf = kernel.string_forge().as_ref(); let sh = kernel.shape_forge().as_ref(); @@ -1114,7 +1121,11 @@ fn bind_set(kernel: &Arc, global: &mut JsObject) { fn bind_map(kernel: &Arc, global: &mut JsObject) { let ctor_ptr = kernel.builtin_world().map_constructor.as_ptr() as *mut JsObject; + let ctor = unsafe { &mut *ctor_ptr }; let proto_ptr = kernel.builtin_world().map_proto.as_ptr() as *mut JsObject; + + ctor.set_native_fn(Some(crate::builtins::map::map_constructor as *const ())); + ctor.set_native_arg_count(1); let proto = unsafe { &mut *proto_ptr }; let sf = kernel.string_forge().as_ref(); let sh = kernel.shape_forge().as_ref(); @@ -1182,7 +1193,7 @@ fn bind_map(kernel: &Arc, global: &mut JsObject) { global.bump_generation(); } -fn bind_boolean(kernel: &Arc) { +fn bind_boolean(kernel: &Arc, global: &mut JsObject) { let ctor_ptr = kernel.builtin_world().boolean_constructor.as_ptr() as *mut JsObject; let ctor = unsafe { &mut *ctor_ptr }; let proto_ptr = kernel.builtin_world().boolean_proto.as_ptr() as *mut JsObject; @@ -1213,6 +1224,13 @@ fn bind_boolean(kernel: &Arc) { crate::builtins::boolean::boolean_prototype_to_string, 0 ); + + let si_b = kernel.string_forge().intern("Boolean").0; + let b_shape = kernel.shape_forge().make_shape(global.shape_id(), si_b); + let b_val = JsValue::from_js_object(ctor_ptr); + global.set_shape_id(b_shape); + global.ensure_hash_props().push(Box::new(b_val)); + global.bump_generation(); } fn bind_function(kernel: &Arc) { @@ -1242,7 +1260,7 @@ pub fn init_kernel_builtins(kernel: &Arc) { bind_date(kernel, global); bind_set(kernel, global); bind_map(kernel, global); - bind_boolean(kernel); + bind_boolean(kernel, global); bind_function(kernel); } diff --git a/crates/oxide_vm/tests/builtins_boolean_tests.rs b/crates/oxide_vm/tests/builtins_boolean_tests.rs index 4e5e0cf..16c986f 100644 --- a/crates/oxide_vm/tests/builtins_boolean_tests.rs +++ b/crates/oxide_vm/tests/builtins_boolean_tests.rs @@ -1,3 +1,4 @@ +use oxide_compiler::compiler::Compiler; use oxide_types::object::JsObject; use oxide_types::value::JsValue; use oxide_vm::builtins::boolean::{ @@ -7,12 +8,31 @@ use oxide_vm::vm::Vm; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; +fn eval(vm: &mut Vm, source: &str) -> Result { + 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))?; + vm.run(&module) +} + +fn str_val(vm: &Vm, val: JsValue) -> String { + vm.kernel() + .string_forge() + .lookup(val.as_string_index()) + .unwrap_or_default() +} + +// -- direct native fn tests -- + fn bool_ctor_call(vm: &mut Vm, arg: JsValue) -> JsValue { vm.set_reg(1, arg); boolean_constructor(vm, &[0, 1]).unwrap() } -fn tbool_wrap(vm: &mut Vm, arg: JsValue) -> JsValue { +fn bool_ctor_new(vm: &mut Vm, arg: JsValue) -> JsValue { let proto_ptr = vm.kernel().builtin_world().boolean_proto.as_ptr() as *mut JsObject; let wrapper = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr)); let ptr = vm.epoch().alloc(wrapper); @@ -23,60 +43,71 @@ fn tbool_wrap(vm: &mut Vm, arg: JsValue) -> JsValue { } #[test] -fn tboolean_call_true() { +fn tboolean_call_true_false() { let mut vm = Vm::new(); assert!(bool_ctor_call(&mut vm, JsValue::float(1.0)).as_bool()); -} - -#[test] -fn tboolean_call_false() { - let mut vm = Vm::new(); assert!(!bool_ctor_call(&mut vm, JsValue::float(0.0)).as_bool()); } #[test] -fn tboolean_call_empty_is_false() { +fn tboolean_call_empty_string() { let mut vm = Vm::new(); let empty = vm.intern(""); assert!(!bool_ctor_call(&mut vm, empty).as_bool()); } #[test] -fn tboolean_new_value_of_true() { +fn tboolean_new_value_of() { let mut vm = Vm::new(); - let wrapper = tbool_wrap(&mut vm, JsValue::float(1.0)); - vm.set_reg(0, wrapper); + let w = bool_ctor_new(&mut vm, JsValue::float(1.0)); + vm.set_reg(0, w); assert!(boolean_prototype_value_of(&mut vm, &[0]).unwrap().as_bool()); -} - -#[test] -fn tboolean_new_value_of_false() { - let mut vm = Vm::new(); - let wrapper = tbool_wrap(&mut vm, JsValue::float(0.0)); - vm.set_reg(0, wrapper); + let w2 = bool_ctor_new(&mut vm, JsValue::float(0.0)); + vm.set_reg(0, w2); assert!(!boolean_prototype_value_of(&mut vm, &[0]).unwrap().as_bool()); } #[test] fn tboolean_new_tostring() { let mut vm = Vm::new(); - let w = tbool_wrap(&mut vm, JsValue::float(1.0)); + let w = bool_ctor_new(&mut vm, JsValue::float(1.0)); vm.set_reg(0, w); let r = boolean_prototype_to_string(&mut vm, &[0]).unwrap(); - let s = vm - .kernel() - .string_forge() - .lookup(r.as_string_index()) - .unwrap(); - assert_eq!(s, "true"); - - let w2 = tbool_wrap(&mut vm, JsValue::float(0.0)); + assert_eq!(str_val(&vm, r), "true"); + let w2 = bool_ctor_new(&mut vm, JsValue::float(0.0)); vm.set_reg(0, w2); let r2 = boolean_prototype_to_string(&mut vm, &[0]).unwrap(); - let s2 = vm - .kernel() - .string_forge() - .lookup(r2.as_string_index()) - .unwrap(); - assert_eq!(s2, "false"); + assert_eq!(str_val(&vm, r2), "false"); +} + +// -- JS eval tests using new keyword -- + +#[test] +fn boolean_call_true() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "Boolean(1)").unwrap(); + assert!(r.as_bool()); +} + +#[test] +fn boolean_call_false() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "Boolean(0)").unwrap(); + assert!(!r.as_bool()); +} + +#[test] +fn boolean_new_value_of() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "new Boolean(1).valueOf()").unwrap(); + assert!(r.as_bool()); +} + +#[test] +fn boolean_new_to_string() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "new Boolean(true).toString()").unwrap(); + assert_eq!(str_val(&vm, r), "true"); + let r = eval(&mut vm, "new Boolean(false).toString()").unwrap(); + assert_eq!(str_val(&vm, r), "false"); } diff --git a/crates/oxide_vm/tests/builtins_date_tests.rs b/crates/oxide_vm/tests/builtins_date_tests.rs index b9713eb..adffa66 100644 --- a/crates/oxide_vm/tests/builtins_date_tests.rs +++ b/crates/oxide_vm/tests/builtins_date_tests.rs @@ -11,35 +11,128 @@ fn eval(vm: &mut Vm, source: &str) -> Result String { + vm.kernel() + .string_forge() + .lookup(val.as_string_index()) + .unwrap_or_default() +} + +// -- static methods -- + #[test] fn date_now_returns_number() { let mut vm = Vm::new(); - let result = eval(&mut vm, "Date.now()").unwrap(); - assert!(result.is_double()); - assert!(result.as_double() > 0.0); -} - -#[test] -fn date_now_increases() { - let mut vm = Vm::new(); - let t1 = eval(&mut vm, "Date.now()").unwrap().as_double(); - let t2 = eval(&mut vm, "Date.now()").unwrap().as_double(); - assert!(t2 >= t1); -} - -#[test] -fn date_parse_returns_nan() { - let mut vm = Vm::new(); - let result = eval(&mut vm, "Date.parse('invalid-date')").unwrap(); - assert!(result.is_double()); - assert!(result.as_double().is_nan()); + let r = eval(&mut vm, "Date.now()").unwrap(); + assert!(r.is_double() && r.as_double() > 0.0); } #[test] fn date_parse_iso_format() { let mut vm = Vm::new(); - let result = eval(&mut vm, "Date.parse('2020-01-15')").unwrap(); - assert!(result.is_double()); - assert!(!result.as_double().is_nan()); - assert!(result.as_double() > 0.0); + let r = eval(&mut vm, "Date.parse('2020-01-15')").unwrap(); + assert!(!r.as_double().is_nan()); +} + +#[test] +fn date_parse_invalid() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "Date.parse('nope')").unwrap(); + assert!(r.as_double().is_nan()); +} + +// -- new Date() constructor via JS -- + +#[test] +fn date_new_multi_arg() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "new Date(2020, 0, 15).getFullYear()").unwrap(); + assert_eq!(r.as_double(), 2020.0); +} + +#[test] +fn date_new_epoch_zero() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "new Date(0).getTime()").unwrap(); + assert_eq!(r.as_double(), 0.0); +} + +#[test] +fn date_new_get_month() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "new Date(2020, 5, 15).getMonth()").unwrap(); + assert_eq!(r.as_double(), 5.0); +} + +#[test] +fn date_new_get_date() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "new Date(2020, 1, 29).getDate()").unwrap(); + assert_eq!(r.as_double(), 29.0); +} + +#[test] +fn date_new_get_hours_min_sec() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "new Date(2020, 0, 1, 12, 30, 45).getHours()").unwrap(); + assert_eq!(r.as_double(), 12.0); + let r = eval(&mut vm, "new Date(2020, 0, 1, 12, 30, 45).getMinutes()").unwrap(); + assert_eq!(r.as_double(), 30.0); + let r = eval(&mut vm, "new Date(2020, 0, 1, 12, 30, 45).getSeconds()").unwrap(); + assert_eq!(r.as_double(), 45.0); +} + +#[test] +fn date_set_time() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "var d = new Date(0); d.setTime(1000); d.getTime()").unwrap(); + assert_eq!(r.as_double(), 1000.0); +} + +#[test] +fn date_set_full_year() { + let mut vm = Vm::new(); + let r = eval( + &mut vm, + "var d = new Date(2020, 0, 1); d.setFullYear(2025); d.getFullYear()", + ) + .unwrap(); + assert_eq!(r.as_double(), 2025.0); +} + +#[test] +fn date_value_of() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "new Date(2020, 0, 1).valueOf()").unwrap(); + assert!(r.as_double() > 0.0); +} + +#[test] +fn date_to_iso_string() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "new Date(2020, 0, 1, 0, 0, 0).toISOString()").unwrap(); + let s = str_val(&vm, r); + assert!(s.starts_with("2020-01-01"), "got: {s}"); +} + +#[test] +fn date_to_json() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "new Date(2020, 0, 1).toJSON()").unwrap(); + let s = str_val(&vm, r); + assert!(s.starts_with("2020-01-01"), "got: {s}"); +} + +#[test] +fn date_to_string_not_empty() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "new Date(0).toString()").unwrap(); + assert!(r.is_string()); +} + +#[test] +fn date_to_utc_string_not_empty() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "new Date(0).toUTCString()").unwrap(); + assert!(r.is_string()); } diff --git a/crates/oxide_vm/tests/builtins_map_tests.rs b/crates/oxide_vm/tests/builtins_map_tests.rs index e5c6f52..9d4b2e7 100644 --- a/crates/oxide_vm/tests/builtins_map_tests.rs +++ b/crates/oxide_vm/tests/builtins_map_tests.rs @@ -1,9 +1,29 @@ +use oxide_compiler::compiler::Compiler; use oxide_types::value::JsValue; use oxide_vm::builtins::map::{ map_clear, map_constructor as new_map, map_delete, map_get, map_has, map_set, map_size, }; use oxide_vm::vm::Vm; +fn eval(vm: &mut Vm, source: &str) -> Result { + 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))?; + vm.run(&module) +} + +fn str_val(vm: &Vm, val: JsValue) -> String { + vm.kernel() + .string_forge() + .lookup(val.as_string_index()) + .unwrap_or_default() +} + +// -- direct native fn tests -- + #[test] fn tmap_constructor_returns_object() { let mut vm = Vm::new(); @@ -20,37 +40,18 @@ fn tmap_set_get() { vm.set_reg(2, JsValue::float(100.0)); map_set(&mut vm, &[0, 1, 2]).unwrap(); assert_eq!(map_get(&mut vm, &[0, 1]).unwrap().as_double(), 100.0); + assert!(map_get(&mut vm, &[0, 99]).unwrap().is_undefined()); } #[test] -fn tmap_get_missing() { - let mut vm = Vm::new(); - let m = new_map(&mut vm, &[0]).unwrap(); - vm.set_reg(0, m); - vm.set_reg(1, JsValue::float(99.0)); - assert!(map_get(&mut vm, &[0, 1]).unwrap().is_undefined()); -} - -#[test] -fn tmap_has_check() { - let mut vm = Vm::new(); - let m = new_map(&mut vm, &[0]).unwrap(); - vm.set_reg(0, m); - vm.set_reg(1, JsValue::float(1.0)); - vm.set_reg(2, JsValue::float(2.0)); - map_set(&mut vm, &[0, 1, 2]).unwrap(); - assert!(map_has(&mut vm, &[0, 1]).unwrap().as_bool()); - assert!(!map_has(&mut vm, &[0, 99]).unwrap().as_bool()); -} - -#[test] -fn tmap_delete_works() { +fn tmap_has_and_delete() { let mut vm = Vm::new(); let m = new_map(&mut vm, &[0]).unwrap(); vm.set_reg(0, m); vm.set_reg(1, JsValue::float(7.0)); vm.set_reg(2, JsValue::float(8.0)); map_set(&mut vm, &[0, 1, 2]).unwrap(); + assert!(map_has(&mut vm, &[0, 1]).unwrap().as_bool()); assert!(map_delete(&mut vm, &[0, 1]).unwrap().as_bool()); assert!(!map_has(&mut vm, &[0, 1]).unwrap().as_bool()); } @@ -71,3 +72,39 @@ fn tmap_size_and_clear() { map_clear(&mut vm, &[0]).unwrap(); assert_eq!(map_size(&mut vm, &[0]).unwrap().as_double(), 0.0); } + +// -- JS eval tests using new keyword (all in one eval per test) -- + +#[test] +fn map_new_set_get() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "var m = new Map(); m.set('k', 'v'); m.get('k')").unwrap(); + assert_eq!(str_val(&vm, r), "v"); +} + +#[test] +fn map_new_get_missing() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "var m = new Map(); m.get('nope')").unwrap(); + assert!(r.is_undefined()); +} + +#[test] +fn map_new_has() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "var m = new Map(); m.set('a', 1); m.has('a')").unwrap(); + assert!(r.as_bool()); + let r = eval(&mut vm, "var m2 = new Map(); m2.has('b')").unwrap(); + assert!(!r.as_bool()); +} + +#[test] +fn map_new_delete() { + let mut vm = Vm::new(); + let r = eval( + &mut vm, + "var m = new Map(); m.set('x', 10); m.delete('x'); m.has('x')", + ) + .unwrap(); + assert!(!r.as_bool()); +} diff --git a/crates/oxide_vm/tests/builtins_set_tests.rs b/crates/oxide_vm/tests/builtins_set_tests.rs index 05e0bad..f742e1a 100644 --- a/crates/oxide_vm/tests/builtins_set_tests.rs +++ b/crates/oxide_vm/tests/builtins_set_tests.rs @@ -1,9 +1,22 @@ +use oxide_compiler::compiler::Compiler; use oxide_types::value::JsValue; use oxide_vm::builtins::set::{ set_add, set_clear, set_constructor as new_set, set_delete, set_has, set_size, }; use oxide_vm::vm::Vm; +fn eval(vm: &mut Vm, source: &str) -> Result { + 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))?; + vm.run(&module) +} + +// -- direct native fn tests -- + #[test] fn tset_constructor_returns_object() { let mut vm = Vm::new(); @@ -17,7 +30,7 @@ fn tset_add_has() { let s = new_set(&mut vm, &[0]).unwrap(); vm.set_reg(0, s); vm.set_reg(1, JsValue::float(42.0)); - let _ = set_add(&mut vm, &[0, 1]).unwrap(); + set_add(&mut vm, &[0, 1]).unwrap(); assert!(set_has(&mut vm, &[0, 1]).unwrap().as_bool()); } @@ -42,7 +55,7 @@ fn tset_delete_works() { } #[test] -fn tset_size_increases() { +fn tset_size_and_clear() { let mut vm = Vm::new(); let s = new_set(&mut vm, &[0]).unwrap(); vm.set_reg(0, s); @@ -52,15 +65,6 @@ fn tset_size_increases() { vm.set_reg(1, JsValue::float(2.0)); set_add(&mut vm, &[0, 1]).unwrap(); assert_eq!(set_size(&mut vm, &[0]).unwrap().as_double(), 2.0); -} - -#[test] -fn tset_clear_works() { - let mut vm = Vm::new(); - let s = new_set(&mut vm, &[0]).unwrap(); - vm.set_reg(0, s); - vm.set_reg(1, JsValue::float(1.0)); - set_add(&mut vm, &[0, 1]).unwrap(); set_clear(&mut vm, &[0]).unwrap(); assert_eq!(set_size(&mut vm, &[0]).unwrap().as_double(), 0.0); } @@ -88,3 +92,37 @@ fn tset_signed_zero() { set_add(&mut vm, &[0, 1]).unwrap(); assert_eq!(set_size(&mut vm, &[0]).unwrap().as_double(), 1.0); } + +// -- JS eval tests using new keyword -- + +#[test] +fn set_new_empty() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "var s = new Set(); s").unwrap(); + assert!(r.is_object()); +} + +#[test] +fn set_new_add_has() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "var s = new Set(); s.add(1); s.has(1)").unwrap(); + assert!(r.as_bool()); +} + +#[test] +fn set_new_has_missing() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "var s = new Set(); s.has(99)").unwrap(); + assert!(!r.as_bool()); +} + +#[test] +fn set_new_delete() { + let mut vm = Vm::new(); + let r = eval( + &mut vm, + "var s = new Set(); s.add(1); s.add(2); s.delete(1); s.has(1)", + ) + .unwrap(); + assert!(!r.as_bool()); +}