use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use std::time::{SystemTime, UNIX_EPOCH};
struct Fixture(PathBuf);
impl Fixture {
fn new() -> Self {
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("clock after Unix epoch")
.as_nanos();
let root =
std::env::temp_dir().join(format!("noxid-adapt-assets-{}-{nonce}", std::process::id()));
fs::create_dir_all(&root).expect("create fixture root");
Self(root)
}
fn noxid(&self, directory: &Path, args: &[&str]) -> Output {
Command::new(env!("CARGO_BIN_EXE_noxid"))
.args(args)
.current_dir(directory)
.env_remove("VERCEL")
.env_remove("NETLIFY")
.env_remove("RAILWAY_ENVIRONMENT")
.output()
.expect("run noxid")
}
}
impl Drop for Fixture {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
fn assert_ok(output: &Output, what: &str) {
assert!(
output.status.success(),
"{what} failed:\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn adapt_ships_declared_static_assets() {
let fixture = Fixture::new();
let scaffold = fixture.noxid(
&fixture.0,
&["new", "site", "--template", "counter", "--render", "client"],
);
assert_ok(&scaffold, "noxid new");
let project = fixture.0.join("site");
let manifest = project.join("Noxid.toml");
let mut toml = fs::read_to_string(&manifest).expect("read Noxid.toml");
if !toml.contains("[assets]") {
toml.push_str("\n[assets]\ndirectory = \"src/assets\"\n");
fs::write(&manifest, toml).expect("write Noxid.toml");
}
let assets = project.join("src/assets");
fs::create_dir_all(&assets).expect("create assets dir");
let svg = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 8 8\"><circle r=\"4\" cx=\"4\" cy=\"4\"/></svg>";
fs::write(assets.join("mark.svg"), svg).expect("write asset");
let build = fixture.noxid(&project, &["build", ".", "--out-dir", "built"]);
assert_ok(&build, "noxid build");
assert_eq!(
fs::read_to_string(project.join("built/assets/mark.svg")).expect("built asset"),
svg,
"noxid build copies the declared asset"
);
let adapt = fixture.noxid(
&project,
&["adapt", ".", "--out-dir", "deployed", "--adapter", "node"],
);
assert_ok(&adapt, "noxid adapt");
assert_eq!(
fs::read_to_string(project.join("deployed/assets/mark.svg")).expect("adapted asset"),
svg,
"noxid adapt ships the declared asset byte for byte"
);
}
#[test]
fn adapt_refuses_an_asset_that_collides_with_generated_output() {
let fixture = Fixture::new();
let scaffold = fixture.noxid(
&fixture.0,
&["new", "site", "--template", "counter", "--render", "client"],
);
assert_ok(&scaffold, "noxid new");
let project = fixture.0.join("site");
let manifest = project.join("Noxid.toml");
let mut toml = fs::read_to_string(&manifest).expect("read Noxid.toml");
if !toml.contains("[assets]") {
toml.push_str("\n[assets]\ndirectory = \"src/assets\"\n");
fs::write(&manifest, toml).expect("write Noxid.toml");
}
let assets = project.join("src/assets");
fs::create_dir_all(&assets).expect("create assets dir");
fs::write(assets.join("global.css"), "body{}").expect("write colliding asset");
let adapt = fixture.noxid(
&project,
&["adapt", ".", "--out-dir", "deployed", "--adapter", "node"],
);
assert!(!adapt.status.success(), "adapt must refuse the collision");
let stderr = String::from_utf8_lossy(&adapt.stderr);
assert!(
stderr.contains("PROJECT_ASSET_COLLISION"),
"expected PROJECT_ASSET_COLLISION, got:\n{stderr}"
);
}