This commit is contained in:
Brandon Thomas
2026-10-23 17:21:16 -04:00
2 changed files with 37 additions and 1 deletions
+5 -1
View File
@@ -935,7 +935,11 @@ fn snapshot(args: &[String]) -> Result<(), String> {
fn commands(args: &[String]) -> Result<(), String> { fn commands(args: &[String]) -> Result<(), String> {
let mut h = Headless::demo(); let mut h = Headless::demo();
let cmds = h.call("engine.commands", json!({}))?; let mut cmds = h.call("engine.commands", json!({}))?;
// Listed by id so each family (`photo.*`, `mask.*`, …) reads together; the registry keeps its own order.
if let Some(list) = cmds.as_array_mut() {
list.sort_by(|a, b| a["id"].as_str().unwrap_or("").cmp(b["id"].as_str().unwrap_or("")));
}
let mut out = std::io::stdout().lock(); let mut out = std::io::stdout().lock();
if args.iter().any(|a| a == "--json") { if args.iter().any(|a| a == "--json") {
writeln!(out, "{}", serde_json::to_string_pretty(&cmds).unwrap_or_default()).map_err(|e| e.to_string())?; writeln!(out, "{}", serde_json::to_string_pretty(&cmds).unwrap_or_default()).map_err(|e| e.to_string())?;
+32
View File
@@ -286,6 +286,38 @@ fn commands_subcommand_lists_registry() {
assert!(String::from_utf8_lossy(&o.stdout).contains("light.exposure")); assert!(String::from_utf8_lossy(&o.stdout).contains("light.exposure"));
} }
#[test]
fn commands_subcommand_lists_commands_sorted_by_id() {
let o = Command::new(BIN).args(["commands", "--json"]).output().unwrap();
assert!(o.status.success());
let v: Value = serde_json::from_slice(&o.stdout).unwrap();
let cmds = v.as_array().unwrap();
for c in cmds {
for field in ["id", "label", "menu", "shortcut", "params", "enabled"] {
assert!(c.get(field).is_some(), "every command should keep its {field}: {c}");
}
}
let ids: Vec<&str> = cmds.iter().filter_map(|c| c["id"].as_str()).collect();
assert_eq!(ids.len(), cmds.len(), "every command should have a string id");
let unsorted = ids.windows(2).find(|w| w[0] >= w[1]);
assert!(unsorted.is_none(), "--json should list commands sorted by id, but {unsorted:?} are out of order");
let o = Command::new(BIN).arg("commands").output().unwrap();
assert!(o.status.success());
let text = String::from_utf8_lossy(&o.stdout);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(lines.len(), 2 * ids.len(), "each command should take an id line and a params line");
let listed: Vec<&str> = lines.iter().step_by(2).filter_map(|l| l.split_whitespace().next()).collect();
assert_eq!(listed, ids, "the text output should list the same commands in the same order as --json");
assert!(lines.iter().skip(1).step_by(2).all(|l| l.trim_start().starts_with("params:")), "each id line should be followed by its params line");
for (line, c) in lines.iter().step_by(2).zip(cmds) {
assert!(line.contains(c["label"].as_str().unwrap()), "{line:?} should show the label of {c}");
if let Some(sc) = c["shortcut"].as_str() {
assert!(line.contains(&format!("[{sc}]")), "{line:?} should show the shortcut of {c}");
}
}
}
#[test] #[test]
fn snapshot_subcommand_renders_the_ui_headlessly() { fn snapshot_subcommand_renders_the_ui_headlessly() {
let script = tmp("snap.jsonl"); let script = tmp("snap.jsonl");