use bao_engine::context::JsContext;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
fn test_01_job_queue_init_empty_state(ctx: &mut JsContext) {
let result = ctx.eval("42", "test_empty.js");
assert!(result.is_ok(), "eval on fresh queue should succeed");
}
fn test_02_job_queue_enqueue_and_drain(ctx: &mut JsContext) {
let result = ctx.eval(
r#"
var resolved = false;
Promise.resolve(10).then(function(v) { resolved = v === 10; });
"done"
"#,
"drain_test.js",
);
assert!(result.is_ok(), "promise drain should not error");
}
fn test_03_job_queue_fifo_ordering(ctx: &mut JsContext) {
let result = ctx.eval(
r#"
var order = [];
Promise.resolve().then(function() { order.push(1); });
Promise.resolve().then(function() { order.push(2); });
Promise.resolve().then(function() { order.push(3); });
order.join(",")
"#,
"fifo_parallel.js",
);
assert!(result.is_ok());
let result = ctx.eval(
r#"
var result = [];
Promise.resolve(0)
.then(function() { result.push("a"); })
.then(function() { result.push("b"); })
.then(function() { result.push("c"); });
"check"
"#,
"fifo_chain.js",
);
assert!(result.is_ok());
}
fn test_04_job_queue_capacity_length(ctx: &mut JsContext) {
let result = ctx.eval(
r#"
Promise.resolve(1).then(function() {});
Promise.resolve(2).then(function() {});
"enqueued"
"#,
"capacity.js",
);
assert!(result.is_ok());
let result2 = ctx.eval("'after'", "capacity2.js");
assert!(result2.is_ok());
}
fn test_06_eval_simple_expressions(ctx: &mut JsContext) {
let val = ctx.eval("1 + 2", "arith.js").expect("1+2 should succeed");
assert!(val.is_number(), "1+2 should produce a number");
assert_eq!(val.as_number().unwrap(), 3.0);
let val = ctx
.eval("'hello' + ' ' + 'world'", "string.js")
.expect("string concat should succeed");
assert!(val.is_string(), "concat should produce a string");
assert_eq!(val.as_string().unwrap(), "hello world");
let val = ctx.eval("true", "bool.js").expect("true should succeed");
assert!(
val.as_bool().unwrap_or(false) || val.is_number(),
"true should be bool or number"
);
let val = ctx
.eval("undefined", "undef.js")
.expect("undefined should succeed");
assert!(val.is_undefined());
let val = ctx.eval("null", "null.js").expect("null should succeed");
assert!(val.is_null());
}
fn test_07_eval_syntax_error(ctx: &mut JsContext) {
let err = ctx
.eval("function(", "syntax_err.js")
.expect_err("syntax error should be Err");
assert!(!err.message.is_empty(), "error message should not be empty");
assert_eq!(err.filename, "syntax_err.js");
}
fn test_07_eval_reference_error(ctx: &mut JsContext) {
let err = ctx
.eval("nonexistentVariable", "ref_err.js")
.expect_err("reference error should be Err");
assert!(!err.message.is_empty());
}
fn test_07_eval_throw_error(ctx: &mut JsContext) {
let err = ctx
.eval("throw new Error('test error')", "throw.js")
.expect_err("throw should be Err");
assert!(
err.message.contains("test error"),
"message should contain 'test error', got: {}",
err.message
);
}
fn test_08_global_setup_hook(ctx: &mut JsContext) {
static CALLED: AtomicBool = AtomicBool::new(false);
unsafe fn setup_hook(
_cx: &mut mozjs::context::JSContext,
_global: mozjs::rust::Handle<*mut mozjs::jsapi::JSObject>,
) {
CALLED.store(true, Ordering::SeqCst);
}
static LATE_CALLED: AtomicBool = AtomicBool::new(false);
unsafe fn late_setup_hook(
_cx: &mut mozjs::context::JSContext,
_global: mozjs::rust::Handle<*mut mozjs::jsapi::JSObject>,
) {
LATE_CALLED.store(true, Ordering::SeqCst);
}
ctx.set_global_setup(late_setup_hook);
assert!(ctx.global_setup().is_some());
assert!(ctx.eval("1", "setup_late.js").is_ok());
assert!(
!LATE_CALLED.load(Ordering::SeqCst),
"setup registered after the realm's first eval must NOT re-fire"
);
let mut fresh = JsContext::for_test().expect("second for_test on same thread");
fresh.set_global_setup(setup_hook);
assert!(fresh.global_setup().is_some());
let result = fresh.eval("1", "setup_test.js");
assert!(result.is_ok());
assert!(
CALLED.load(Ordering::SeqCst),
"global_setup should have been called at the fresh realm's birth"
);
}
fn test_09_post_eval_hook(ctx: &mut JsContext) {
static HOOK_CALLED: AtomicBool = AtomicBool::new(false);
fn hook(_cx: &mut mozjs::context::JSContext) -> bool {
HOOK_CALLED.store(true, Ordering::SeqCst);
false
}
ctx.set_post_eval_hook(hook);
assert!(ctx.post_eval_hook().is_some());
let result = ctx.eval("42", "hook_test.js");
assert!(result.is_ok());
assert!(
HOOK_CALLED.load(Ordering::SeqCst),
"post_eval_hook should have been called"
);
static CALL_COUNT: AtomicUsize = AtomicUsize::new(0);
fn counting_hook(_cx: &mut mozjs::context::JSContext) -> bool {
CALL_COUNT.fetch_add(1, Ordering::SeqCst);
false
}
ctx.set_post_eval_hook(counting_hook);
ctx.eval("1", "count1.js").unwrap();
ctx.eval("2", "count2.js").unwrap();
ctx.eval("3", "count3.js").unwrap();
assert_eq!(
CALL_COUNT.load(Ordering::SeqCst),
3,
"hook should fire once per eval"
);
}
fn test_10_cx_mut(ctx: &mut JsContext) {
let _cx = ctx.cx();
let result = ctx.eval("'cx_ok'", "cx_test.js");
assert!(result.is_ok());
assert_eq!(result.unwrap().as_string().unwrap(), "cx_ok");
}
fn test_extra_object_and_array(ctx: &mut JsContext) {
let val = ctx.eval("({a: 1, b: 2})", "obj.js").unwrap();
assert!(val.is_object());
let val = ctx.eval("[1, 2, 3]", "arr.js").unwrap();
assert!(val.is_object(), "JS arrays are objects in SpiderMonkey");
}
fn test_extra_eval_isolation(ctx: &mut JsContext) {
ctx.eval("var x = 100;", "state1.js").unwrap();
let result = ctx.eval("x === 100", "state2.js").unwrap();
assert!(
result.as_bool().unwrap_or(false),
"var state must persist across evals on the same context (one realm)"
);
let mut other = JsContext::for_test().expect("for_test for isolation check");
let result = other.eval("typeof x === 'undefined'", "state3.js").unwrap();
assert!(
result.as_bool().unwrap_or(false),
"contexts are isolated (each has its own realm global)"
);
}
#[test]
fn job_queue_and_context_integration() {
let mut ctx = JsContext::for_test().expect("JsContext::for_test() should succeed");
test_01_job_queue_init_empty_state(&mut ctx);
test_02_job_queue_enqueue_and_drain(&mut ctx);
test_03_job_queue_fifo_ordering(&mut ctx);
test_04_job_queue_capacity_length(&mut ctx);
test_06_eval_simple_expressions(&mut ctx);
test_07_eval_syntax_error(&mut ctx);
test_07_eval_reference_error(&mut ctx);
test_07_eval_throw_error(&mut ctx);
test_08_global_setup_hook(&mut ctx);
test_09_post_eval_hook(&mut ctx);
test_10_cx_mut(&mut ctx);
test_extra_object_and_array(&mut ctx);
test_extra_eval_isolation(&mut ctx);
bao_engine::context::JsContext::shutdown_thread_sm();
}