//! The binary end to end. #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] use std::io::Write; use std::process::{Command, Stdio}; fn bin() -> Command { Command::new(env!("CARGO_BIN_EXE_figcraft-cli")) } fn tmp(name: &str) -> std::path::PathBuf { let d = std::env::temp_dir().join(format!("figcraft-cli-test-{}-{name}", std::process::id())); std::fs::create_dir_all(&d).unwrap(); d } #[test] fn bad_arguments_exit_2_without_panicking() { for args in [ &[][..], &["bogus"], &["export"], &["export", "x.figcraft"], &["info", "--nope"], &["render", "a", "-o"], &["perf", "--shapes", "lots"], &["mcp", "--attach", "notaport"], &["convert", "a"], ] { let out = bin().args(args).output().unwrap(); assert_eq!(out.status.code(), Some(2), "{args:?}: {}", String::from_utf8_lossy(&out.stderr)); let err = String::from_utf8_lossy(&out.stderr); assert!(err.contains("USAGE"), "{args:?}"); assert!(!err.contains("panicked")); } let out = bin().args(["info", "/no/such/file.figcraft"]).output().unwrap(); assert_eq!(out.status.code(), Some(1)); } #[test] fn run_export_info_convert() { let d = tmp("run"); let script = d.join("s.jsonl"); std::fs::write(&script, "{\"command\":\"frame.create\",\"params\":{\"x\":0,\"y\":0,\"width\":100,\"height\":50,\"name\":\"A\",\"props\":{\"fill\":\"#ff0000\"}}}\n{\"command\":\"frame.create\",\"params\":{\"x\":200,\"y\":0,\"width\":30,\"height\":30,\"name\":\"B\"}}\n").unwrap(); let file = d.join("doc.figcraft"); let out = bin().arg("run").arg(&script).arg("--out").arg(&file).output().unwrap(); assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr)); let lines: Vec = String::from_utf8_lossy(&out.stdout).lines().map(|l| serde_json::from_str(l).unwrap()).collect(); assert_eq!(lines.len(), 2); assert!(lines.iter().all(|l| l["ok"] == true)); let info = bin().args(["info", "--json"]).arg(&file).output().unwrap(); let v: serde_json::Value = serde_json::from_slice(&info.stdout).unwrap(); assert_eq!(v["pages"][0]["frames"].as_array().unwrap().len(), 2); let dir = d.join("png"); assert!(bin().arg("export").arg(&file).arg("-o").arg(&dir).args(["--scale", "2"]).status().unwrap().success()); let a = std::fs::read(dir.join("A.png")).unwrap(); assert_eq!(&a[16..24], &[0, 0, 0, 200, 0, 0, 0, 100]); assert!(dir.join("B.png").exists()); let svg = d.join("b.svg"); assert!(bin().arg("export").arg(&file).arg("-o").arg(&svg).args(["--node", "B"]).status().unwrap().success()); assert!(std::fs::read_to_string(&svg).unwrap().contains(" = String::from_utf8_lossy(&out.stdout).lines().map(|l| serde_json::from_str(l).unwrap()).collect(); assert_eq!(lines.len(), 3); assert_eq!(lines[0]["result"]["protocolVersion"], "2025-06-18"); assert_eq!(lines[1]["error"]["code"], -32700); assert_eq!(lines[2]["result"]["isError"], false); } #[test] fn commands_json_lists_everything() { let out = bin().args(["commands", "--json"]).output().unwrap(); let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap(); assert!(v.as_array().unwrap().len() > 200); }