use bao_engine::context::JsContext;
use bao_engine::value::JsValue;
fn eval_string(ctx: &mut JsContext, source: &str) -> String {
match ctx.eval(source, "<test>") {
Ok(JsValue::String(s)) => s,
Ok(JsValue::Number(n)) => format!("{}", n),
Ok(JsValue::Bool(b)) => if b { "true" } else { "false" }.to_string(),
Ok(JsValue::Null) => "null".to_string(),
Ok(JsValue::Undefined) => "undefined".to_string(),
Ok(JsValue::Object(_)) => "[object]".to_string(),
Err(e) => format!("ERROR:{}", e.message),
}
}
fn eval_bool(ctx: &mut JsContext, source: &str) -> bool {
match ctx.eval(source, "<test>") {
Ok(JsValue::Bool(b)) => b,
_ => false,
}
}
unsafe fn install_test_globals(
cx: &mut mozjs::context::JSContext,
global: mozjs::rust::Handle<*mut mozjs::jsapi::JSObject>,
) {
bao_engine::host_fn::install_console(cx, global);
}
#[test]
fn test_error_handling_deep() {
let mut ctx = JsContext::for_test().expect("JsContext");
ctx.set_global_setup(install_test_globals);
let result = eval_string(
&mut ctx,
r#"
try { throw new Error("native error"); } catch(e) { e.message }
"#,
);
assert_eq!(result, "native error", "Error message should match");
let result = eval_string(
&mut ctx,
r#"
try { null.foo; } catch(e) { e.constructor.name }
"#,
);
assert_eq!(result, "TypeError", "null access should throw TypeError");
let result = eval_string(
&mut ctx,
r#"
try { new Array(-1); } catch(e) { e.constructor.name }
"#,
);
assert!(
result.contains("RangeError"),
"negative Array length should throw RangeError, got: {}",
result
);
let result = eval_string(
&mut ctx,
r#"
try { eval("function("); } catch(e) { e.constructor.name }
"#,
);
assert_eq!(result, "SyntaxError", "bad eval should throw SyntaxError");
let result = eval_string(
&mut ctx,
r#"
try { undeclaredVariable; } catch(e) { e.constructor.name }
"#,
);
assert_eq!(
result, "ReferenceError",
"undeclared variable should throw ReferenceError"
);
let result = eval_string(
&mut ctx,
r#"
var e = new Error("test_msg"); e.message
"#,
);
assert_eq!(result, "test_msg");
let result = eval_string(
&mut ctx,
r#"
var e = new Error("x"); e.name
"#,
);
assert_eq!(result, "Error");
assert!(
eval_bool(
&mut ctx,
r#"
var e = new Error("stack_test"); typeof e.stack === 'string' && e.stack.length > 0
"#
),
"Error should have non-empty stack"
);
let result = eval_string(
&mut ctx,
r#"
class CustomError extends Error {
constructor(msg) { super(msg); this.name = "CustomError"; }
}
try { throw new CustomError("custom"); } catch(e) { e.name + ':' + e.message }
"#,
);
assert_eq!(result, "CustomError:custom");
let result = eval_string(
&mut ctx,
r#"
var r = "";
try { r += "try"; throw "err"; } catch(e) { r += "_" + e; }
r
"#,
);
assert_eq!(result, "try_err");
let result = eval_string(
&mut ctx,
r#"
var r = "";
try { r += "try"; } finally { r += "_finally"; }
r
"#,
);
assert_eq!(result, "try_finally");
let result = eval_string(
&mut ctx,
r#"
var r = "";
try { r += "try"; throw "x"; } catch(e) { r += "_catch"; } finally { r += "_finally"; }
r
"#,
);
assert_eq!(result, "try_catch_finally");
let result = eval_string(
&mut ctx,
r#"
var r = "";
try {
try { throw "inner"; } catch(e) { r += e; }
throw "outer";
} catch(e) { r += "_" + e; }
r
"#,
);
assert_eq!(result, "inner_outer");
let result = ctx.eval(")))", "<syntax-test>");
assert!(result.is_err(), "syntax error should return Err");
let err = result.unwrap_err();
assert!(!err.message.is_empty(), "error message should be non-empty");
assert_eq!(err.filename, "<syntax-test>");
let result = ctx.eval("throw new Error('from engine')", "<runtime-test>");
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("from engine"));
let result = eval_string(
&mut ctx,
r#"
function thrower() { throw new Error("func_error"); }
try { thrower(); } catch(e) { e.message }
"#,
);
assert_eq!(result, "func_error");
let result = eval_string(
&mut ctx,
r#"
function inner() { throw new Error("deep"); }
function outer() { inner(); }
try { outer(); } catch(e) { e.message }
"#,
);
assert_eq!(result, "deep");
assert!(eval_bool(&mut ctx, "typeof Promise.reject === 'function'"));
let result = eval_string(
&mut ctx,
r#"
try {
[1,2,3].map(function(x) { throw new Error("map_err"); });
} catch(e) { e.message }
"#,
);
assert_eq!(result, "map_err");
let result = eval_string(
&mut ctx,
r#"
var caught = false;
try { JSON.parse("invalid{json"); } catch(e) { caught = true; }
caught ? "caught" : "not_caught"
"#,
);
assert_eq!(result, "caught");
let result = eval_string(
&mut ctx,
r#"
try { throw 42; } catch(e) { String(e) }
"#,
);
assert_eq!(result, "42");
let result = eval_string(
&mut ctx,
r#"
try { throw "string_error"; } catch(e) { e }
"#,
);
assert_eq!(result, "string_error");
let result = eval_string(
&mut ctx,
r#"
try { throw {code: 500, msg: "fail"}; } catch(e) { e.code }
"#,
);
assert_eq!(result, "500");
let result = eval_string(
&mut ctx,
r#"
try { throw undefined; } catch(e) { String(e) }
"#,
);
assert_eq!(result, "undefined");
let result = eval_string(
&mut ctx,
r#"
try { throw null; } catch(e) { String(e) }
"#,
);
assert_eq!(result, "null");
let result = eval_string(
&mut ctx,
r#"
new Error("test").toString()
"#,
);
assert!(
result.contains("Error") && result.contains("test"),
"Error toString should contain type and message, got: {}",
result
);
let result = eval_string(
&mut ctx,
r#"
var r = "";
try {
try { throw new Error("a"); } catch(e) { r += e.message; throw new Error("b"); }
} catch(e) { r += "_" + e.message; }
r
"#,
);
assert_eq!(result, "a_b");
bao_engine::context::JsContext::shutdown_thread_sm();
}