use rong::JSEngineValue;
use rong_macro::js_export;
use rong_test::*;
#[js_export]
struct Point {
x: i32,
y: i32,
jsobj: Option<JSObject>,
}
impl Point {
fn add(&self, p: Point) -> Self {
Self {
x: self.x + p.x,
y: self.y + p.y,
jsobj: None,
}
}
fn sadd(x: i32, y: i32) -> Self {
Self {
x: x + 1,
y: y + 1,
jsobj: None,
}
}
fn set_jsobj(&mut self, obj: JSObject) {
self.jsobj = Some(obj);
}
}
impl JSClass<JSEngineValue> for Point {
const NAME: &'static str = "Point";
fn data_constructor() -> Constructor<JSEngineValue> {
Constructor::new(|x, y| Point { x, y, jsobj: None })
}
fn class_setup(class: &ClassSetup<JSEngineValue>) -> JSResult<()> {
class.property("x", |builder| {
let getter = class.new_func(|this: This<Point>| this.x)?;
let setter = class.new_func(|this: ThisMut<Point>, x: i32| -> JSResult<()> {
let mut point = this.borrow_mut()?;
point.x = x;
Ok(())
})?;
Ok(builder.getter(getter).setter(setter).configurable(true))
})?;
class.property("y", |builder| {
let getter = class.new_func(|this: This<Point>| this.y)?;
let setter = class.new_func(|this: ThisMut<Point>, y: i32| -> JSResult<()> {
let mut point = this.borrow_mut()?;
point.y = y;
Ok(())
})?;
Ok(builder.getter(getter).setter(setter).configurable(true))
})?;
class.static_property("origin", |builder| {
let getter = class.new_func(|| Point {
x: 0x5a,
y: 0xa5,
jsobj: None,
})?;
let setter = class.new_func(|| {
})?;
Ok(builder.getter(getter).setter(setter).configurable(true))
})?;
class.method("add", |this: This<Point>, p: Point| this.add(p))?;
class.method(
"setJSObj",
|this: ThisMut<Point>, callback: JSObject| -> JSResult<()> {
let mut point = this.borrow_mut()?;
point.set_jsobj(callback);
Ok(())
},
)?;
class.static_method("sadd", |x: i32, y: i32| Self::sadd(x, y))?;
Ok(())
}
fn gc_mark_with<F>(&self, mut mark_fn: F)
where
F: FnMut(&JSValue),
{
if let Some(obj) = &self.jsobj {
mark_fn(obj.as_js_value());
}
}
}
#[test]
fn constructor() {
run(|ctx| {
ctx.register_class::<Point>()?;
let point = ctx.eval::<Point>(Source::from_bytes(b"let point=new Point(2,3); point"))?;
assert_eq!(point.x, 2);
assert_eq!(point.y, 3);
let obj = ctx.eval::<JSObject>(Source::from_bytes(b"new Point(2,3)"))?;
assert!(Class::instance_of::<Point>(&obj));
let obj = ctx.eval::<JSObject>(Source::from_bytes(b"let o = {}; o"))?;
assert!(!Class::instance_of::<Point>(&obj));
assert_eq!(
ctx.eval::<String>(Source::from_bytes(b"Point.constructor.name"))?,
"Function"
);
assert!(ctx.eval::<bool>(Source::from_bytes(b"Point.prototype.constructor==Point"))?);
assert!(ctx.eval::<bool>(Source::from_bytes(b"point instanceof Point"))?);
Ok(())
});
}
#[test]
fn rustfunc_class_registered() {
run(|ctx| {
assert_eq!(
ctx.eval::<String>(Source::from_bytes(b"RustFunc.name"))?,
"RustFunc"
);
assert_eq!(
ctx.eval::<String>(Source::from_bytes(b"RustFunc.constructor.name"))?,
"Function"
);
Ok(())
});
}
#[test]
fn basic_add_fn() {
run(|ctx| {
let func = JSFunc::new(ctx, |a: i32, b: i32, c: i32| a + b + c)?.name("add")?;
ctx.global().set("add", func)?;
assert!(ctx.eval::<JSFunc>(Source::from_bytes(b"add")).is_ok());
assert_eq!(
ctx.eval::<i32>(Source::from_bytes(b"add(7, 9,1)")).unwrap(),
17
);
assert_eq!(
ctx.eval::<i32>(Source::from_bytes(b"add.length")).unwrap(),
3
);
assert_eq!(
ctx.eval::<String>(Source::from_bytes(b"add.name")).unwrap(),
"add"
);
Ok(())
});
}
#[test]
fn test_property_getter_setter() {
run(|ctx| {
ctx.register_class::<Point>()?;
let point = ctx.eval::<Point>(Source::from_bytes(b"let p = new Point(5, 10); p"))?;
assert_eq!(point.x, 5);
let point = ctx
.eval::<Point>(Source::from_bytes(b"p.x = 15; p"))
.unwrap();
assert_eq!(point.x, 15);
assert!(
ctx.eval::<bool>(Source::from_bytes(
b"Object.getOwnPropertyDescriptor(Point.prototype, 'x').configurable"
))
.unwrap()
);
assert!(
ctx.eval::<bool>(Source::from_bytes(
b"Object.getOwnPropertyDescriptor(Point.prototype, 'x').get !== undefined"
))
.unwrap()
);
assert!(
ctx.eval::<bool>(Source::from_bytes(
b"Object.getOwnPropertyDescriptor(Point.prototype, 'x').set !== undefined"
))
.unwrap()
);
Ok(())
});
}
#[test]
fn test_instance_method() {
run(|ctx| {
ctx.register_class::<Point>()?;
let result = ctx
.eval::<Point>(Source::from_bytes(
b"let p1 = new Point(1, 2); let p2 = new Point(3, 4); p1.add(p2)",
))
.unwrap();
assert_eq!(result.x, 4); assert_eq!(result.y, 6);
assert!(
ctx.eval::<bool>(Source::from_bytes(b"'add' in Point.prototype"))
.unwrap()
);
assert!(
ctx.eval::<bool>(Source::from_bytes(
b"typeof Point.prototype.add === 'function'"
))
.unwrap()
);
Ok(())
});
}
#[test]
fn test_static_method() {
run(|ctx| {
ctx.register_class::<Point>()?;
let result = ctx
.eval::<Point>(Source::from_bytes(b"Point.sadd(5, 7)"))
.unwrap();
assert_eq!(result.x, 6); assert_eq!(result.y, 8);
assert!(
ctx.eval::<bool>(Source::from_bytes(b"'sadd' in Point"))
.unwrap()
);
assert!(
ctx.eval::<bool>(Source::from_bytes(b"typeof Point.sadd === 'function'"))
.unwrap()
);
Ok(())
});
}
#[test]
fn test_static_property() {
run(|ctx| {
ctx.register_class::<Point>()?;
let origin = ctx
.eval::<Point>(Source::from_bytes(b"Point.origin"))
.unwrap();
assert_eq!(origin.x, 0x5a);
assert_eq!(origin.y, 0xa5);
assert!(
ctx.eval::<bool>(Source::from_bytes(b"'origin' in Point"))
.unwrap()
);
assert!(
ctx.eval::<bool>(Source::from_bytes(
b"Object.getOwnPropertyDescriptor(Point, 'origin').configurable"
))
.unwrap()
);
assert!(
ctx.eval::<bool>(Source::from_bytes(
b"Object.getOwnPropertyDescriptor(Point, 'origin').get !== undefined"
))
.unwrap()
);
assert!(
ctx.eval::<bool>(Source::from_bytes(
b"Object.getOwnPropertyDescriptor(Point, 'origin').set !== undefined"
))
.unwrap()
);
assert!(
!ctx.eval::<bool>(Source::from_bytes(b"'origin' in Point.prototype"))
.unwrap()
);
assert!(
!ctx.eval::<bool>(Source::from_bytes(b"'origin' in (new Point(0, 0))"))
.unwrap()
);
Ok(())
});
}
#[test]
fn test_extend_class() {
run(|ctx| {
ctx.register_class::<Point>()?;
let result = ctx
.eval::<i32>(Source::from_bytes(
br#"
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y);
this.color = color;
}
get_color() {
return this.color;
}
}
let p = new ColorPoint(2, 3, 0x5fa5);
// Verify prototype chain
if (!(ColorPoint.prototype.__proto__ === Point.prototype)) {
throw new Error('Prototype chain broken');
}
if (!(ColorPoint.__proto__ === Point)) {
throw new Error('Constructor chain broken');
}
// Verify inherited methods work
let added = p.add(new Point(1, 2));
if (added.x !== 3 || added.y !== 5) {
throw new Error('Inherited method failed');
}
// Verify new method works
p.get_color()
"#,
))
.unwrap();
assert_eq!(result, 0x5fa5);
Ok(())
});
}
#[test]
fn test_instance_hold_object_fromjs() {
run(|ctx| {
ctx.register_class::<Point>()?;
ctx.global()
.set("print", JSFunc::new(ctx, |msg: String| println!("{}", msg)))?;
ctx.eval::<()>(Source::from_bytes(
br#"
let point = new Point(10, 20);
point.name="hello";
point.setJSObj( { a:1, });
"#,
))?;
Ok(())
});
}