Files
deckcraft/apps/deckcraft-cli/tests/it/cli.rs
T
aiongg f001f37906 CI: one test binary per crate, nextest, line tables only (#1213)
* Build: line tables only for workspace crates, no debug info for dependencies

* Tests: one integration-test binary per crate

* xtask ci: nextest, doctests, test build without examples, one wasm check
2026-10-23 12:43:34 -04:00

140 lines
5.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use std::process::{Command, Output};
fn cli(args: &[&str]) -> Output {
Command::new(env!("CARGO_BIN_EXE_deckcraft-cli")).args(args).output().unwrap()
}
fn stdout(o: &Output) -> String {
assert!(o.status.success(), "exit {:?}\nstderr: {}", o.status, String::from_utf8_lossy(&o.stderr));
String::from_utf8_lossy(&o.stdout).into_owned()
}
fn stderr(o: &Output) -> String {
assert!(!o.status.success(), "exit {:?}\nstdout: {}", o.status, String::from_utf8_lossy(&o.stdout));
String::from_utf8_lossy(&o.stderr).into_owned()
}
const SUBCOMMANDS: [&str; 11] = ["info", "render", "render-transition", "convert", "run", "commands", "describe", "app", "mcp", "bench", "links"];
fn words(s: &str) -> String {
s.split_whitespace().collect::<Vec<_>>().join(" ")
}
#[test]
fn an_unknown_subcommand_is_named_before_the_overview() {
for args in [vec!["frobnicate"], vec!["help", "frobnicate"], vec!["--info", "--help"]] {
let err = stderr(&cli(&args));
assert!(err.contains("unknown subcommand `") && err.contains("\n\ndeckcraft-cli works on"), "{args:?}: {err}");
}
}
#[test]
fn no_subcommand_shows_the_overview_and_fails() {
assert!(stderr(&cli(&[])).starts_with("deckcraft-cli works on"));
}
#[test]
fn help_lists_every_subcommand_with_a_run_example() {
for flag in ["help", "--help", "-h"] {
let out = stdout(&cli(&[flag]));
for sub in SUBCOMMANDS {
assert!(out.contains(&format!("\n {sub} ")), "{flag} lacks {sub}: {out}");
}
assert!(out.contains("deckcraft-cli run --cmd 'slide.new={\"title\":\"Q3 results\"}' --save q3.deckcraft"), "{out}");
}
}
#[test]
fn every_subcommand_answers_help_and_does_nothing_else() {
let dir = std::env::temp_dir().join(format!("dc-cli-help-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
for sub in SUBCOMMANDS {
for args in [vec![sub, "--help"], vec![sub, "-h", "out.png"], vec!["help", sub]] {
let o = Command::new(env!("CARGO_BIN_EXE_deckcraft-cli")).current_dir(&dir).args(&args).output().unwrap();
assert!(stdout(&o).starts_with(&format!("usage: deckcraft-cli {sub}")), "{args:?}");
}
}
assert_eq!(std::fs::read_dir(&dir).unwrap().count(), 0, "help wrote nothing");
std::fs::remove_dir_all(&dir).unwrap();
for args in [["help", "version"], ["--version", "--help"]] {
assert!(stdout(&cli(&args)).starts_with("usage: deckcraft-cli --version"), "{args:?}");
}
for args in [["help", "help"], ["help", "--help"]] {
assert!(stdout(&cli(&args)).starts_with("deckcraft-cli works on"), "{args:?}");
}
}
#[test]
fn run_help_says_what_the_ids_do_not() {
let out = words(&stdout(&cli(&["run", "--help"])));
for fact in [
"COMMAND is a command id, then `=`, then the command's parameters as one JSON object.",
"points on a 960×540 slide",
"Slide indices start at 0.",
"Shape ids are looked up on the current slide only.",
"A command that adds a shape (shape.insert, insert.textBox, insert.table…) selects it.",
"format.* acts on the selected text or shapes",
] {
assert!(out.contains(fact), "{fact}: {out}");
}
}
#[test]
fn describe_spells_out_a_command() {
assert_eq!(
stdout(&cli(&["describe", "shape.insert"])),
"shape.insert: Shapes, in Insert ▸ Illustrations.
Parameters:
preset rect|roundRect|ellipse|triangle|rightArrow|star5|… (see shape.presets)
rect [x,y,w,h] pt
text, optional string
Run it with: deckcraft-cli run --cmd 'shape.insert={…}'
"
);
assert_eq!(
stdout(&cli(&["describe", "format.size"])),
"format.size: Font Size, in Home ▸ Font.
Parameters:
size pt
Acts on the selected text or shapes.
Run it with: deckcraft-cli run --cmd 'format.size={…}'
"
);
assert!(stdout(&cli(&["describe", "edit.copy"])).contains("Answers with {text, html?}.\n\nRun it with: deckcraft-cli run --cmd edit.copy\n"));
assert!(stdout(&cli(&["describe", "text.set"])).contains("Note: replaces the whole text, keeping the first run's formatting\n"));
assert_eq!(
stdout(&cli(&["describe", "shape.setDefault"])),
"shape.setDefault: Set as Default Shape.\n\nTakes no parameters.\nActs on the selected shapes.\n\nRun it with: deckcraft-cli run --cmd shape.setDefault\n"
);
}
#[test]
fn describe_shows_alternative_sets_of_parameters() {
assert!(stdout(&cli(&["describe", "slide.go"])).contains("Parameters:\n either:\n index\n or:\n id\n"));
let remove = stdout(&cli(&["describe", "animation.remove"]));
assert!(remove.contains(" or:\n ids, optional (all of the shapes' animations)\n"), "{remove}");
assert!(remove.ends_with("Run it with: deckcraft-cli run --cmd animation.remove\n") && !remove.contains("Note:"), "{remove}");
assert!(stdout(&cli(&["describe", "shape.move"])).contains(
" either:
dx, optional pt (relative)
dy, optional pt (relative)
ids, optional
or:
x, optional pt (absolute)
y, optional pt (absolute)
ids, optional
"
));
}
#[test]
fn commands_lists_one_line_per_command() {
assert_eq!(stdout(&cli(&["commands", "format.size"])), "format.size — Font Size: {size: pt}\n");
assert_eq!(stdout(&cli(&["commands", "slide.next"])), "slide.next — Next Slide\n");
assert_eq!(stdout(&cli(&["commands", "shape.presets"])), "shape.presets — Shape Gallery → [{name, label, category}]\n");
}