use bao_engine::context::JsContext;
use bao_engine::value::JsValue;
fn eval_string(ctx: &mut JsContext, source: &str) -> String {
match ctx.eval(source, "<fusion>") {
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(other) => format!("{:?}", other),
Err(e) => format!("<error: {}>", e.message),
}
}
fn eval_bool(ctx: &mut JsContext, source: &str) -> bool {
matches!(ctx.eval(source, "<fusion>"), Ok(JsValue::Bool(true)))
}
fn eval_ok(ctx: &mut JsContext, source: &str) -> bool {
ctx.eval(source, "<fusion>").is_ok()
}
#[test]
fn js_context_fusion_node_and_web_api_coexist() {
bun_runtime::install_exit_handler();
bun_runtime::bun_api::init_process_start();
let mut ctx = JsContext::for_test().expect("JsContext::for_test");
ctx.set_global_setup(bun_runtime::globals::install_all);
let require_type = eval_string(&mut ctx, "typeof require");
assert_eq!(
require_type, "function",
"require must be typeof 'function' after globals::install_all, got '{require_type}'"
);
assert!(
eval_bool(&mut ctx, "typeof Bun === 'object' && Bun !== null"),
"Bun global must be an object"
);
assert!(
eval_bool(&mut ctx, "Bun === Bao"),
"Bao must be same object as Bun (alias)"
);
let buffer_type = eval_string(&mut ctx, "typeof Buffer");
let process_type = eval_string(&mut ctx, "typeof process");
assert_eq!(
buffer_type, "function",
"Buffer must be typeof 'function' (Node Buffer constructor)"
);
assert_eq!(
process_type, "object",
"process must be typeof 'object' (Node process global)"
);
let buf_len = eval_string(&mut ctx, "Buffer.from('hello').length");
let buf_to_str = eval_string(
&mut ctx,
"Buffer.from([104, 105]).toString()", );
assert_eq!(buf_len, "5", "Buffer.from('hello').length must be 5");
assert_eq!(
buf_to_str, "hi",
"Buffer.from([104, 105]).toString() must be 'hi'"
);
assert!(
eval_ok(&mut ctx, "process.env.__BAO_FUSION_TEST = 'yes'"),
"process.env write must succeed"
);
assert_eq!(
eval_string(&mut ctx, "process.env.__BAO_FUSION_TEST"),
"yes",
"process.env read must return the value just written"
);
let cross_api = eval_string(
&mut ctx,
r#"
const bytes = Array.from(Buffer.from('abc'));
const doubled = bytes.map(c => c * 2);
JSON.stringify(doubled)
"#,
);
assert_eq!(
cross_api, "[194,196,198]",
"Node Buffer → array → map → JSON pipeline must work in single context"
);
assert_eq!(
eval_string(&mut ctx, "process.env.__BAO_FUSION_TEST"),
"yes",
"state from §5 must persist — JsContext is singleton across evals"
);
let _ = eval_string(&mut ctx, "Bun.gc()");
let post_gc_buf = eval_string(&mut ctx, "Buffer.from('post-gc').toString()");
assert_eq!(
post_gc_buf, "post-gc",
"JsContext must remain usable after Bun.gc()"
);
let snapshot = eval_string(
&mut ctx,
r#"JSON.stringify({
require: typeof require,
module: typeof module,
exports: typeof exports,
Bun: typeof Bun,
Bao: typeof Bao,
Buffer: typeof Buffer,
process: typeof process,
globalThis: typeof globalThis,
console: typeof console,
TextEncoder: typeof TextEncoder,
TextDecoder: typeof TextDecoder,
URL: typeof URL,
setTimeout: typeof setTimeout,
Promise: typeof Promise
})"#,
);
let v: serde_json::Value =
serde_json::from_str(&snapshot).expect("typeof snapshot must be valid JSON");
let obj = v.as_object().expect("snapshot must be a JSON object");
for key in [
"require",
"module",
"exports",
"Bun",
"Bao",
"Buffer",
"process",
"globalThis",
"console",
"TextEncoder",
"TextDecoder",
"URL",
"setTimeout",
"Promise",
] {
let ty = obj.get(key).and_then(|x| x.as_str()).unwrap_or("<missing>");
assert_ne!(
ty, "undefined",
"global `{key}` must not be undefined in fused JsContext (got typeof={ty})"
);
}
}