use rong_test::function::JSParameterType;
use rong_test::*;
use tokio::time::{Duration, sleep};
#[test]
fn function_with_optional() {
run(|ctx| {
let func = JSFunc::new(ctx, |a: i32, b: Optional<i32>| match *b {
Some(val) => a + val,
None => a,
})?
.name("add_optional")?;
ctx.global().set("add_optional", func)?;
assert_eq!(
ctx.eval::<i32>(Source::from_bytes(b"add_optional(7)"))
.unwrap(),
7
);
assert_eq!(
ctx.eval::<i32>(Source::from_bytes(b"add_optional(7, 3)"))
.unwrap(),
10
);
assert_eq!(
ctx.eval::<i32>(Source::from_bytes(b"add_optional.length"))
.unwrap(),
1
);
Ok(())
});
}
#[test]
fn function_with_rest() {
run(|ctx| {
let func = JSFunc::new(ctx, |init: i32, rest: Rest<i32>| {
let sum: i32 = rest.iter().sum();
init + sum
})?
.name("add")?;
ctx.global().set("add_rest", func)?;
assert_eq!(
ctx.eval::<i32>(Source::from_bytes(b"add_rest(1)")).unwrap(),
1
);
assert_eq!(
ctx.eval::<i32>(Source::from_bytes(b"add_rest(1, 2)"))
.unwrap(),
3
);
assert_eq!(
ctx.eval::<i32>(Source::from_bytes(b"add_rest(1, 2, 3, 4)"))
.unwrap(),
10
);
assert_eq!(
ctx.eval::<i32>(Source::from_bytes(b"add_rest.length"))
.unwrap(),
1
);
Ok(())
});
}
#[test]
fn function_with_optional_and_rest() {
run(|ctx| {
let func = JSFunc::new(ctx, |a: i32, b: Optional<i32>, rest: Rest<i32>| {
let base = match *b {
Some(val) => a + val,
None => a,
};
let sum: i32 = rest.iter().sum();
base + sum
})?
.name("complex_add")?;
ctx.global().set("complex_add", func)?;
assert_eq!(
ctx.eval::<i32>(Source::from_bytes(b"complex_add(1)"))
.unwrap(),
1
);
assert_eq!(
ctx.eval::<i32>(Source::from_bytes(b"complex_add(1, 2)"))
.unwrap(),
3
);
assert_eq!(
ctx.eval::<i32>(Source::from_bytes(b"complex_add(1, 2, 3)"))
.unwrap(),
6
);
assert_eq!(
ctx.eval::<i32>(Source::from_bytes(b"complex_add(1, 2, 3, 4)"))
.unwrap(),
10
);
assert_eq!(
ctx.eval::<i32>(Source::from_bytes(b"complex_add.length"))
.unwrap(),
1
);
Ok(())
});
}
#[test]
fn test_jsfunc_call() {
run(|ctx| {
let rust_func = JSFunc::new(ctx, |a: i32, b: i32| a + b)?;
let result: i32 = rust_func.call(None, (2, 3)).unwrap();
assert_eq!(result, 5);
let js_func: JSFunc = ctx
.eval(Source::from_bytes(b"(function(a, b) { return a * b; })"))
.unwrap();
let result: i32 = js_func.call(None, (4, 5)).unwrap();
assert_eq!(result, 20);
let result: Result<i32, _> = rust_func.call(None, ());
assert!(result.is_err());
Ok(())
});
}
#[test]
fn test_jsfunc_call_macro() {
run(|ctx| {
let rust_func = JSFunc::new(ctx, |a: i32, b: i32| a + b)?;
let result: i32 = rust_func.call(None, (2, 3)).unwrap();
assert_eq!(result, 5);
let rust_func = JSFunc::new(ctx, || 8)?;
let result: i32 = rust_func.call(None, ()).unwrap();
assert_eq!(result, 8);
Ok(())
});
}
#[test]
fn test_jsfunc_as_argument() {
run(|ctx| {
let func = JSFunc::new(ctx, |callback: JSFunc| {
let result: i32 = callback.call(None, (2, 3)).unwrap();
result * 2
})?
.name("call_and_double")?;
ctx.global().set("call_and_double", func)?;
let result: i32 = ctx
.eval(Source::from_bytes(
b"call_and_double(function(a, b) { return a + b; })",
))
.unwrap();
assert_eq!(result, 10);
let result: i32 = ctx
.eval(Source::from_bytes(
b"call_and_double(function(a, b) { return a * b; })",
))
.unwrap();
assert_eq!(result, 12); Ok(())
});
}
#[test]
fn test_async_rust_fn_resolve() {
async_run!(|ctx: JSContext| async move {
let async_func = JSFunc::new(&ctx, |a: i32, b: i32| async move {
sleep(Duration::from_millis(100)).await;
a + b
})?;
ctx.global().set("add", async_func)?;
let result: i32 = ctx
.eval::<Promise>(Source::from_bytes(
b"add(2,6).then(result=>{return result;})",
))?
.into_future()
.await?;
assert_eq!(result, 8);
Ok(())
});
}
#[test]
fn test_async_rust_fn_reject() {
async_run!(|ctx: JSContext| async move {
let async_func = JSFunc::new(&ctx, |_a: i32, _b: i32| async move {
sleep(Duration::from_millis(100)).await;
RongJSError::from(HostError::new(
rong::error::E_ERROR,
"Failed to perform add",
))
})?; ctx.global().set("add", async_func)?;
let result = ctx
.eval::<Promise>(Source::from_bytes(
br#"add(2,6)
.then((resolve) => {return resolve;})
.catch(err =>{ throw new Error(err+"!");})
"#,
))?
.into_future::<i32>()
.await;
let err = result.unwrap_err();
let message = thrown_error_message(&ctx, &err)?;
assert!(message.contains("Failed to perform add!"));
Ok(())
});
}
#[test]
fn test_new_once() {
run(|ctx| {
let func = JSFunc::new_once(ctx, |x: i32| x + 1)?;
ctx.global().set("once", func)?;
let result = ctx.eval::<i32>(Source::from_bytes("once(2); once(3)"));
let err = result.unwrap_err();
let message = thrown_error_message(ctx, &err)?;
assert!(message.contains("OnceFn had been called"));
Ok(())
});
}
#[test]
fn test_new_once_async() {
async_run!(|ctx: JSContext| async move {
let set_timeout = JSFunc::new_once(&ctx, |callback: JSFunc, delay: u32| async move {
tokio::time::sleep(Duration::from_millis(delay as u64)).await;
callback.call::<_, ()>(None, ()).unwrap();
})?;
ctx.global().set("setTimeout", set_timeout)?;
let js_code = r#"
new Promise((resolve) => {
setTimeout(() => {
resolve(42);
}, 100);
setTimeout(() => {
resolve(42);
}, 100);
})
"#;
let promise = ctx
.eval::<Promise>(Source::from_bytes(js_code.as_bytes()))
.unwrap();
let result: JSResult<i32> = promise.into_future().await;
let err = result.unwrap_err();
let message = thrown_error_message(&ctx, &err)?;
assert!(message.contains("OnceFn had been called"));
tokio::time::sleep(Duration::from_millis(110)).await;
Ok(())
})
}
#[test]
fn test_call_async_sync_return() {
async_run!(|ctx: JSContext| async move {
let js_func: JSFunc = ctx
.eval(Source::from_bytes(b"(function(a, b) { return a + b; })"))
.unwrap();
let result: i32 = js_func.call_async(None, (2, 3)).await?;
assert_eq!(result, 5);
Ok(())
});
}
#[test]
fn test_call_async_promise_return() {
async_run!(|ctx: JSContext| async move {
let sleep_fn = JSFunc::new(&ctx, |ms: u32| async move {
sleep(Duration::from_millis(ms as u64)).await;
})?;
ctx.global().set("sleep", sleep_fn)?;
let js_func: JSFunc = ctx
.eval(Source::from_bytes(
b"(async function(a, b) {
await sleep(100);
return a * b;
})",
))
.unwrap();
let result: i32 = js_func.call_async(None, (4, 5)).await?;
assert_eq!(result, 20);
Ok(())
});
}
#[test]
fn test_call_async_with_this() {
async_run!(|ctx: JSContext| async move {
let sleep_fn = JSFunc::new(&ctx, |ms: u32| async move {
sleep(Duration::from_millis(ms as u64)).await;
})?;
ctx.global().set("sleep", sleep_fn)?;
let obj: JSObject = ctx
.eval(Source::from_bytes(
b"({
value: 10,
asyncMethod: async function(x) {
await sleep(50);
return this.value + x;
}
})",
))
.unwrap();
let method: JSFunc = obj.get("asyncMethod")?;
let result: i32 = method.call_async(Some(obj.clone()), (5,)).await?;
assert_eq!(result, 15);
Ok(())
});
}
#[test]
fn test_call_async_error_handling() {
async_run!(|ctx: JSContext| async move {
let sleep_fn = JSFunc::new(&ctx, |ms: u32| async move {
sleep(Duration::from_millis(ms as u64)).await;
})?;
ctx.global().set("sleep", sleep_fn)?;
let js_func: JSFunc = ctx
.eval(Source::from_bytes(
b"(async function() {
await sleep(50);
throw new Error('test error');
})",
))
.unwrap();
let result: Result<i32, _> = js_func.call_async(None, ()).await;
assert!(result.is_err());
Ok(())
});
}
#[test]
fn function_return_vec_is_js_array() {
run(|ctx| {
let func = JSFunc::new(ctx, || -> Vec<i32> { vec![1, 2, 3] })?;
ctx.global().set("make_vec", func)?;
let ok: bool = ctx.eval(Source::from_bytes(
br#"
(function () {
const arr = make_vec();
return Array.isArray(arr)
&& arr.length === 3
&& arr[0] === 1
&& arr[1] === 2
&& arr[2] === 3;
})()
"#,
))?;
assert!(ok);
Ok(())
});
}
#[test]
fn function_param_vec_js_array() {
run(|ctx| {
let func = JSFunc::new(ctx, |nums: Vec<i32>| -> i32 { nums.into_iter().sum() })?;
ctx.global().set("sum_vec", func)?;
let result: i32 = ctx.eval(Source::from_bytes(b"sum_vec([1,2,3,4])")).unwrap();
assert_eq!(result, 10);
Ok(())
});
}
#[derive(Clone)]
struct Job {
id: i32,
}
impl IntoJSValue<JSEngineValue> for Job {
fn into_js_value(self, ctx: &JSContext) -> JSValue {
let obj = JSObject::new(ctx);
obj.set("id", self.id).unwrap();
obj.into_js_value()
}
}
impl FromJSValue<JSEngineValue> for Job {
fn from_js_value(ctx: &JSContext, value: JSValue) -> JSResult<Self> {
let obj = JSObject::from_js_value(ctx, value)?;
let id: i32 = obj.get("id")?;
Ok(Job { id })
}
}
impl JSParameterType for Job {}
fn sum_optional_jobs(_ctx: JSContext, items: Optional<Vec<Job>>) -> i32 {
match items.0 {
Some(v) => v.into_iter().map(|j| j.id).sum(),
None => 0,
}
}
#[test]
fn function_param_context_optional_vec() {
run(|ctx| {
let f = JSFunc::new(ctx, sum_optional_jobs)?;
ctx.global().set("sum_optional_jobs", f)?;
let r0: i32 = ctx.eval(Source::from_bytes(b"sum_optional_jobs()"))?;
assert_eq!(r0, 0);
let r1: i32 = ctx.eval(Source::from_bytes(b"sum_optional_jobs([{id:3},{id:4}])"))?;
assert_eq!(r1, 7);
Ok(())
});
}
#[test]
fn function_param_custom_struct() {
run(|ctx| {
let f = JSFunc::new(ctx, |j: Job, opt: Optional<i32>| -> i32 {
j.id + (*opt).unwrap_or_default()
})?;
ctx.global().set("use_job", f)?;
let r0: i32 = ctx.eval(Source::from_bytes(b"use_job({id:7})"))?;
assert_eq!(r0, 7);
let r1: i32 = ctx.eval(Source::from_bytes(b"use_job({id:7}, 5)"))?;
assert_eq!(r1, 12);
Ok(())
});
}
#[test]
fn function_param_vec_of_custom_struct() {
run(|ctx| {
let sum = JSFunc::new(ctx, |items: Vec<Job>| -> i32 {
items.into_iter().map(|j| j.id).sum()
})?;
ctx.global().set("sum_jobs", sum)?;
let res: i32 = ctx.eval(Source::from_bytes(b"sum_jobs([{id:1}, {id:2}, {id:3}])"))?;
assert_eq!(res, 6);
Ok(())
});
}
#[test]
fn host_function_inherits_function_prototype() {
run(|ctx| {
let add = JSFunc::new(ctx, |a: i32, b: i32| a + b)?;
ctx.global().set("add_host", add)?;
let t_call: String = ctx.eval(Source::from_bytes(b"typeof add_host.call"))?;
let t_apply: String = ctx.eval(Source::from_bytes(b"typeof add_host.apply"))?;
let t_bind: String = ctx.eval(Source::from_bytes(b"typeof add_host.bind"))?;
assert_eq!(t_call, "function");
assert_eq!(t_apply, "function");
assert_eq!(t_bind, "function");
let r1: i32 = ctx.eval(Source::from_bytes(b"add_host.call(null, 1, 2)"))?;
assert_eq!(r1, 3);
let r2: i32 = ctx.eval(Source::from_bytes(b"add_host.apply(null, [10, 5])"))?;
assert_eq!(r2, 15);
let r3: i32 = ctx.eval(Source::from_bytes(b"add_host.bind(null, 7)(8)"))?;
assert_eq!(r3, 15);
Ok(())
});
}
#[test]
fn host_async_function_call_apply() {
async_run!(|ctx: JSContext| async move {
let fa = JSFunc::new(&ctx, |a: i32| async move { a * 2 })?;
ctx.global().set("fa_host", fa)?;
let out1: i32 = ctx
.eval_async(Source::from_bytes(b"fa_host.call(null, 6)"))
.await?;
assert_eq!(out1, 12);
let out2: i32 = ctx
.eval_async(Source::from_bytes(b"fa_host.apply(null, [9])"))
.await?;
assert_eq!(out2, 18);
Ok(())
});
}