mod common;
use common::Sandbox;
use serde_json::Value;
fn stack_aliases(c: &Sandbox) -> Vec<String> {
let s = c.ok(&["stack", "--json"]);
let v: Value = serde_json::from_str(&s).unwrap_or_else(|e| panic!("not JSON: {e}\n{s}"));
v["stack"]
.as_array()
.expect("stack is an array")
.iter()
.map(|n| n["alias"].as_str().unwrap().to_string())
.collect()
}
fn state_of(c: &Sandbox, id: &str) -> String {
let s = c.ok(&["why", id, "--json"]);
let v: Value = serde_json::from_str(&s).unwrap_or_else(|e| panic!("not JSON: {e}\n{s}"));
v["node"]["state"].as_str().unwrap().to_string()
}
#[test]
fn push_root_with_a_stack_leaves_only_the_new_node_on_it() {
let c = Sandbox::new_seeded("root-push-stack");
c.ok(&["push", "First goal", "--why", "it came first"]);
c.ok(&["push", "A branch", "--why", "spotted along the way"]);
c.ok(&["push", "Deeper still", "--why", "one more level"]);
assert_eq!(stack_aliases(&c), vec!["g1", "t2", "t3"]);
let out = c.ok(&["push", "The successor", "--why", "g1 is done for", "--root"]);
assert!(out.contains(" g4 The successor"), "{out}");
assert!(
out.contains(" at the root: 3 left the stack, g1 to t3, none closed by this"),
"{out}"
);
assert!(
out.contains(" back there with: vivac focus t3"),
"{out}"
);
assert_eq!(stack_aliases(&c), vec!["g4"]);
assert_eq!(state_of(&c, "g1"), "active");
assert_eq!(state_of(&c, "t2"), "active");
assert_eq!(state_of(&c, "t3"), "active");
}
#[test]
fn push_root_with_a_single_node_prints_the_short_line() {
let c = Sandbox::new_seeded("root-push-single");
c.ok(&["push", "First goal", "--why", "it came first"]);
let out = c.ok(&["push", "The successor", "--why", "moving on", "--root"]);
assert!(
out.contains(" at the root: g1 left the stack, not closed by this"),
"{out}"
);
assert!(
!out.contains("left the stack, g1 to"),
"it used the plural line for a single node:\n{out}"
);
assert!(
out.contains(" back there with: vivac focus g1"),
"{out}"
);
}
#[test]
fn push_root_with_an_empty_stack_matches_plain_push() {
let sandbox_plain = Sandbox::new_seeded("root-push-empty-plain");
let plain = sandbox_plain.ok(&["push", "First goal", "--why", "it came first"]);
let sandbox_root = Sandbox::new_seeded("root-push-empty-root");
let with_root = sandbox_root.ok(&["push", "First goal", "--why", "it came first", "--root"]);
assert_eq!(plain, with_root);
}
#[test]
fn add_root_and_decide_root_leave_the_stack_alone() {
let c = Sandbox::new_seeded("root-add-decide");
c.ok(&["push", "First goal", "--why", "it came first"]);
let before = stack_aliases(&c);
let s = c.ok(&[
"add",
"A stray finding",
"--why",
"noticed in passing",
"--root",
]);
assert!(s.contains("(root)"), "{s}");
let v: Value = serde_json::from_str(&c.ok(&["why", "2", "--json"])).unwrap();
assert_eq!(v["node"]["parent"], Value::Null);
assert_eq!(stack_aliases(&c), before);
c.ok(&[
"decide",
"Adopt the new approach",
"--reason",
"it settles the question",
"--root",
]);
let v: Value = serde_json::from_str(&c.ok(&["why", "3", "--json"])).unwrap();
assert_eq!(v["node"]["parent"], Value::Null);
assert_eq!(stack_aliases(&c), before);
}
#[test]
fn root_with_parent_is_refused_on_add_and_decide() {
let c = Sandbox::new_seeded("root-parent-conflict");
c.ok(&["push", "First goal", "--why", "it came first"]);
let before = c.log();
let (out, code) = c.run(&[
"add",
"A stray finding",
"--parent",
"1",
"--why",
"noticed in passing",
"--root",
]);
assert_eq!(code, 2, "{out}");
assert!(
out.contains("--root and --parent both say where it is born"),
"{out}"
);
assert_eq!(before, c.log(), "add wrote despite the conflict");
let (out, code) = c.run(&[
"decide",
"Adopt the new approach",
"--reason",
"it settles the question",
"--parent",
"1",
"--root",
]);
assert_eq!(code, 2, "{out}");
assert!(
out.contains("--root and --parent both say where it is born"),
"{out}"
);
assert_eq!(before, c.log(), "decide wrote despite the conflict");
}