use std::time::Duration;
use bevy_react::js_thread::spawn_js_thread;
use bevy_react::protocol::{Op, Outbound, UiEvent};
use bevy_react::{RawRequest, ReactMessage};
const APP: &[u8] = br#"
(function () {
// Re-execution (hot update): the isolate is alive, so __n is preserved. Report
// it and re-park the event loop (the previous loop returned on the reload).
if (globalThis.__started) {
Deno.core.ops.op_emit("phase", "update:" + globalThis.__n);
globalThis.__loop();
return;
}
// Cold start.
globalThis.__started = true;
globalThis.__n = 41;
globalThis.__loop = () => (async () => {
for (;;) {
const m = await Deno.core.ops.op_next_event();
if (m == null) return; // shutdown
if (m.t === "reload") return; // yield to Rust so it can re-exec the app
if (m.t === "uiEvent") {
globalThis.__n++;
Deno.core.ops.op_emit("phase", "click:" + globalThis.__n);
}
}
})();
Deno.core.ops.op_emit("phase", "init:" + globalThis.__n);
globalThis.__loop();
})();
"#;
fn click(tx: &tokio::sync::mpsc::UnboundedSender<Outbound>) {
tx.send(Outbound::UiEvent {
event: UiEvent {
id: 1,
kind: "click".into(),
..Default::default()
},
})
.expect("JS thread gone");
}
#[test]
fn hot_reload_preserves_isolate_state() {
let dir = std::env::temp_dir().join("bevy_react_hot_reload_test");
std::fs::create_dir_all(&dir).expect("mkdir");
std::fs::write(dir.join("vendor.js"), b"// no-op vendor\n").expect("write vendor");
let app = dir.join("app.js");
std::fs::write(&app, APP).expect("write app");
let (ops_tx, _ops_rx) = crossbeam_channel::unbounded::<Vec<Op>>();
let (emit_tx, emit_rx) = crossbeam_channel::unbounded::<ReactMessage>();
let (request_tx, _request_rx) = crossbeam_channel::unbounded::<RawRequest>();
let (anim_tx, _anim_rx) = crossbeam_channel::unbounded();
let (outbound_tx, outbound_rx) = tokio::sync::mpsc::unbounded_channel::<Outbound>();
let (reload_tx, reload_rx) = tokio::sync::mpsc::unbounded_channel::<()>();
spawn_js_thread(
dir.join("vendor.js"),
app,
ops_tx,
emit_tx,
request_tx,
anim_tx,
outbound_rx,
reload_rx,
);
let recv = || {
emit_rx
.recv_timeout(Duration::from_secs(10))
.expect("no emit")
.value
.as_str()
.unwrap()
.to_string()
};
assert_eq!(recv(), "init:41");
click(&outbound_tx);
assert_eq!(recv(), "click:42");
reload_tx.send(()).expect("send reload");
assert_eq!(
recv(),
"update:42",
"global state lost across reload — isolate was torn down, not refreshed"
);
click(&outbound_tx);
assert_eq!(
recv(),
"click:43",
"event loop did not resume after hot reload"
);
eprintln!("OK isolate persisted across hot reload; state preserved and loop resumed");
}