use boa_gc::Gc;
use boa_parser::Source;
use crate::{
Context, JsObject, JsResult, JsValue,
builtins::{OrdinaryObject, function::OrdinaryFunction},
js_string,
object::{
ObjectInitializer,
internal_methods::InternalMethodPropertyContext,
shape::{WeakShape, slot::SlotAttributes},
},
property::{Attribute, PropertyDescriptor, PropertyKey},
vm::CodeBlock,
};
#[test]
fn get_own_property_internal_method() {
let context = &mut Context::default();
let o = context
.intrinsics()
.templates()
.ordinary_object()
.create(OrdinaryObject, Vec::default());
let property: PropertyKey = js_string!("prop").into();
let value = 100;
o.set(property.clone(), value, true, context)
.expect("should not fail");
let context = &mut InternalMethodPropertyContext::new(context);
assert_eq!(context.slot().index, 0);
assert_eq!(context.slot().attributes, SlotAttributes::empty());
o.__get_own_property__(&property, context)
.expect("should not fail");
assert!(
!context.slot().in_prototype(),
"Since it's an owned property, the prototype bit should not be set"
);
assert!(
context.slot().is_cachable(),
"Since it's an owned property, this should be cachable"
);
let shape = o.borrow().shape().clone();
let slot = shape.lookup(&property);
assert!(slot.is_some(), "the property should be found in the object");
let slot = slot.expect("the property should be found in the object");
assert_eq!(context.slot().index, slot.index);
}
#[test]
fn get_internal_method() {
let context = &mut Context::default();
let o = context
.intrinsics()
.templates()
.ordinary_object()
.create(OrdinaryObject, Vec::default());
let property: PropertyKey = js_string!("prop").into();
let value = 100;
o.set(property.clone(), value, true, context)
.expect("should not fail");
let context = &mut InternalMethodPropertyContext::new(context);
assert_eq!(context.slot().index, 0);
assert_eq!(context.slot().attributes, SlotAttributes::empty());
o.__get__(&property, o.clone().into(), context)
.expect("should not fail");
assert!(
!context.slot().in_prototype(),
"Since it's an owned property, the prototype bit should not be set"
);
assert!(
context.slot().is_cachable(),
"Since it's an owned property, this should be cachable"
);
let shape = o.borrow().shape().clone();
let slot = shape.lookup(&property);
assert!(slot.is_some(), "the property should be found in the object");
let slot = slot.expect("the property should be found in the object");
assert_eq!(context.slot().index, slot.index);
}
#[test]
fn get_internal_method_in_prototype() {
let context = &mut Context::default();
let o = context
.intrinsics()
.templates()
.ordinary_object()
.create(OrdinaryObject, Vec::default());
let property: PropertyKey = js_string!("prop").into();
let value = 100;
let prototype = context.intrinsics().constructors().object().prototype();
prototype
.set(property.clone(), value, true, context)
.expect("should not fail");
let context = &mut InternalMethodPropertyContext::new(context);
assert_eq!(context.slot().index, 0);
assert_eq!(context.slot().attributes, SlotAttributes::empty());
o.__get__(&property, o.clone().into(), context)
.expect("should not fail");
assert!(
context.slot().in_prototype(),
"Since it's an prototype property, the prototype bit should not be set"
);
assert!(
context.slot().is_cachable(),
"Since it's an prototype property, this should be cachable"
);
let shape = prototype.borrow().shape().clone();
let slot = shape.lookup(&property);
assert!(slot.is_some(), "the property should be found in the object");
let slot = slot.expect("the property should be found in the object");
assert_eq!(context.slot().index, slot.index);
}
#[test]
fn define_own_property_internal_method_non_existant_property() {
let context = &mut Context::default();
let o = context
.intrinsics()
.templates()
.ordinary_object()
.create(OrdinaryObject, Vec::default());
let property: PropertyKey = js_string!("prop").into();
let value = 100;
o.set(property.clone(), value, true, context)
.expect("should not fail");
let context = &mut InternalMethodPropertyContext::new(context);
assert_eq!(context.slot().index, 0);
assert_eq!(context.slot().attributes, SlotAttributes::empty());
o.__define_own_property__(
&property,
PropertyDescriptor::builder()
.value(value)
.writable(true)
.configurable(true)
.enumerable(true)
.build(),
context,
)
.expect("should not fail");
assert!(
!context.slot().in_prototype(),
"Since it's an owned property, the prototype bit should not be set"
);
assert!(
context.slot().is_cachable(),
"Since it's an owned property, this should be cachable"
);
let shape = o.borrow().shape().clone();
let slot = shape.lookup(&property);
assert!(slot.is_some(), "the property should be found in the object");
let slot = slot.expect("the property should be found in the object");
assert_eq!(context.slot().index, slot.index);
}
#[test]
fn define_own_property_internal_method_existing_property_property() {
let context = &mut Context::default();
let o = context
.intrinsics()
.templates()
.ordinary_object()
.create(OrdinaryObject, Vec::default());
let property: PropertyKey = js_string!("prop").into();
let value = 100;
o.set(property.clone(), value, true, context)
.expect("should not fail");
o.__define_own_property__(
&property,
PropertyDescriptor::builder()
.value(value)
.writable(true)
.configurable(true)
.enumerable(true)
.build(),
&mut context.into(),
)
.expect("should not fail");
let context = &mut InternalMethodPropertyContext::new(context);
assert_eq!(context.slot().index, 0);
assert_eq!(context.slot().attributes, SlotAttributes::empty());
o.__define_own_property__(
&property,
PropertyDescriptor::builder()
.value(value + 100)
.writable(true)
.configurable(true)
.enumerable(true)
.build(),
context,
)
.expect("should not fail");
assert!(
!context.slot().in_prototype(),
"Since it's an owned property, the prototype bit should not be set"
);
assert!(
context.slot().is_cachable(),
"Since it's an owned property, this should be cachable"
);
let shape = o.borrow().shape().clone();
let slot = shape.lookup(&property);
assert!(slot.is_some(), "the property should be found in the object");
let slot = slot.expect("the property should be found in the object");
assert_eq!(context.slot().index, slot.index);
}
#[test]
fn set_internal_method() {
let context = &mut Context::default();
let o = context
.intrinsics()
.templates()
.ordinary_object()
.create(OrdinaryObject, Vec::default());
let property: PropertyKey = js_string!("prop").into();
let value = 100;
o.set(property.clone(), value, true, context)
.expect("should not fail");
let context = &mut InternalMethodPropertyContext::new(context);
assert_eq!(context.slot().index, 0);
assert_eq!(context.slot().attributes, SlotAttributes::empty());
o.__set__(property.clone(), value.into(), o.clone().into(), context)
.expect("should not fail");
assert!(
!context.slot().in_prototype(),
"Since it's an owned property, the prototype bit should not be set"
);
assert!(
context.slot().is_cachable(),
"Since it's an owned property, this should be cachable"
);
let shape = o.borrow().shape().clone();
let slot = shape.lookup(&property);
assert!(slot.is_some(), "the property should be found in the object");
let slot = slot.expect("the property should be found in the object");
assert_eq!(context.slot().index, slot.index);
}
fn get_codeblock(value: &JsValue) -> Option<(JsObject, Gc<CodeBlock>)> {
let object = value.as_object()?.clone();
let code = object.downcast_ref::<OrdinaryFunction>()?.code.clone();
Some((object, code))
}
#[test]
fn set_property_by_name_set_inline_cache_on_property_load() -> JsResult<()> {
let context = &mut Context::default();
let function = context.eval(Source::from_bytes("(function (o) { return o.test; })"))?;
let (function, code) = get_codeblock(&function).unwrap();
assert_eq!(code.ic.len(), 1);
assert_eq!(code.ic[0].shape.borrow().clone(), WeakShape::None);
let o = ObjectInitializer::new(context)
.property(js_string!("test"), 0, Attribute::all())
.build();
let o_shape = o.borrow().shape().clone();
function.call(&JsValue::undefined(), &[o.clone().into()], context)?;
assert_eq!(code.ic[0].shape.borrow().clone(), WeakShape::from(&o_shape));
Ok(())
}
#[test]
fn get_property_by_name_set_inline_cache_on_property_load() -> JsResult<()> {
let context = &mut Context::default();
let function = context.eval(Source::from_bytes("(function (o) { o.test = 30; })"))?;
let (function, code) = get_codeblock(&function).unwrap();
assert_eq!(code.ic.len(), 1);
assert_eq!(code.ic[0].shape.borrow().clone(), WeakShape::None);
let o = ObjectInitializer::new(context)
.property(js_string!("test"), 0, Attribute::all())
.build();
let o_shape = o.borrow().shape().clone();
function.call(&JsValue::undefined(), &[o.clone().into()], context)?;
assert_eq!(code.ic[0].shape.borrow().clone(), WeakShape::from(&o_shape));
Ok(())
}