use std::path::PathBuf;
use std::process::Command;
fn example_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../examples/hello")
.canonicalize()
.expect("examples/hello path")
}
fn deps_installed(root: &std::path::Path) -> bool {
root.join("node_modules/preact").exists()
&& root.join("node_modules/@nowaki-dev/runtime").exists()
}
#[test]
fn build_produces_expected_manifest_and_zero_js_live_island() {
let root = example_root();
if !deps_installed(&root) {
eprintln!("skip: examples/hello deps not installed");
return;
}
let bin = env!("CARGO_BIN_EXE_nowaki");
let _ = std::fs::remove_dir_all(root.join("dist"));
let status = Command::new(bin)
.arg("build")
.arg(&root)
.status()
.expect("run nowaki build");
assert!(status.success(), "nowaki build failed");
let client = root.join("dist/client");
let manifest = std::fs::read_to_string(client.join("manifest.json")).expect("manifest");
assert!(manifest.contains("\"preload\""), "preload chains expected");
assert!(
manifest.contains("\"liveIslands\": [\"LiveCounter\"]"),
"LiveCounter should be a live island"
);
assert!(
manifest.contains("\"liveRuntime\""),
"liveRuntime (live.js) expected"
);
let names: Vec<String> = std::fs::read_dir(&client)
.unwrap()
.map(|e| e.unwrap().file_name().to_string_lossy().into_owned())
.collect();
assert!(
!names.iter().any(|n| n.starts_with("LiveCounter.")),
"live island must not ship a client chunk"
);
assert!(
!names.iter().any(|n| n.starts_with("cycle-a.")),
"cyclic lib should be hoisted, not a separate chunk"
);
assert!(
root.join("dist/server/islands/LiveCounter.js").exists(),
"live island server module expected"
);
let functions =
std::fs::read_to_string(root.join("dist/server/functions.json")).expect("functions.json");
for export in ["addTodo", "listTodos", "whoami"] {
assert!(
functions.contains(&format!("\"export\": \"{export}\"")),
"server function {export} should be in functions.json"
);
}
assert!(
functions.contains("\"module\": \"actions/todos.js\""),
"functions.json module should point at the built server module"
);
for chunk in &names {
if chunk.ends_with(".js") {
let body = std::fs::read_to_string(client.join(chunk)).unwrap_or_default();
assert!(
!body.contains("nowaki-server-only-secret"),
"server secret leaked into client chunk {chunk}"
);
}
}
let todos_proxy = names
.iter()
.find(|n| n.starts_with("todos.") && n.ends_with(".js"))
.expect("server-fn proxy chunk (todos.*.js) expected");
let proxy = std::fs::read_to_string(client.join(todos_proxy)).unwrap();
assert!(
proxy.contains("__nowakiCall(") && proxy.contains("/__nowaki/fn"),
"proxy chunk should be the fetch shim, not the impl"
);
for id in ["675f80133a1699f8", "6c90d7dc921a9a3f", "781de5a7f5ba2469"] {
assert!(functions.contains(id), "functions.json missing id {id}");
assert!(proxy.contains(id), "proxy missing id {id}");
}
let vbadge = names
.iter()
.find(|n| n.starts_with("VirtualBadge.") && n.ends_with(".js"))
.expect("VirtualBadge client chunk expected");
let vbody = std::fs::read_to_string(client.join(vbadge)).unwrap();
assert!(
vbody.contains("VIRTUAL_OK"),
"virtual module source should be bundled into the island chunk"
);
assert!(
!vbody.contains("virtual:build-info"),
"bare virtual specifier should be inlined, not left as an import"
);
let vserver =
std::fs::read_to_string(root.join("dist/server/islands/VirtualBadge.js")).unwrap();
assert!(
vserver.contains("data:text/javascript,"),
"virtual module should be inlined as a data: module for SSR"
);
assert!(
!vserver.contains("\"virtual:build-info\""),
"no raw virtual specifier should remain in the server module"
);
let _ = std::fs::remove_dir_all(root.join("dist"));
}