use super::*;
use crate::reads::test_support::{git_store, task};
#[test]
fn resolve_dead_reconstructs_a_closed_ball_from_history() {
let s = git_store();
let mut t = task("Refactor", 100);
t.body = "the plan".into();
s.create("bl-1", &t, 100).retire("bl-1", "close", 500);
let dead = resolve_dead(s.dir(), "bl-1").unwrap().unwrap();
assert_eq!(dead.id, "bl-1");
assert_eq!(dead.task.title, "Refactor");
assert_eq!(dead.task.body, "the plan"); assert_eq!(dead.retired_at, 500); }
#[test]
fn resolve_dead_reconstructs_a_dropped_ball_the_same_as_a_closed_one() {
let s = git_store();
s.create("bl-2", &task("Abandoned", 1), 1).retire("bl-2", "drop", 9);
let dead = resolve_dead(s.dir(), "bl-2").unwrap().unwrap();
assert_eq!(dead.task.title, "Abandoned");
assert_eq!(dead.retired_at, 9);
}
#[test]
fn resolve_dead_is_none_when_the_id_was_never_deleted() {
let s = git_store();
s.create("bl-live", &task("Alive", 1), 1); assert!(resolve_dead(s.dir(), "bl-live").unwrap().is_none());
assert!(resolve_dead(s.dir(), "bl-never").unwrap().is_none());
}
#[test]
fn resolve_dead_takes_the_newest_incarnation_of_a_reused_id() {
let s = git_store();
s.create("bl-r", &task("First", 1), 1).retire("bl-r", "close", 2);
s.create("bl-r", &task("Second", 3), 3).retire("bl-r", "drop", 4);
let dead = resolve_dead(s.dir(), "bl-r").unwrap().unwrap();
assert_eq!(dead.task.title, "Second"); assert_eq!(dead.retired_at, 4);
}
#[test]
fn resolve_dead_sees_a_deletion_that_shares_its_commit_with_a_similar_add() {
let s = git_store();
let mut gone = task("Retired", 1);
gone.body = "the plan\nstep one\nstep two\nstep three\n".into();
let mut born = task("Successor", 2);
born.body = gone.body.clone(); s.create("bl-gone", &gone, 1).retire_and_create("bl-gone", "bl-new", &born, 50);
let dead = resolve_dead(s.dir(), "bl-gone").unwrap().unwrap();
assert_eq!(dead.task.title, "Retired"); assert_eq!(dead.retired_at, 50);
}
#[test]
fn resolve_dead_surfaces_a_corrupt_historical_file_as_an_error() {
let s = git_store();
s.create_raw("bl-bad", "not valid frontmatter", 1).retire("bl-bad", "close", 2);
assert!(resolve_dead(s.dir(), "bl-bad").is_err());
}
#[test]
fn resolve_dead_errors_when_the_store_is_not_a_git_repo() {
let tmp = tempfile::TempDir::new().unwrap();
assert!(resolve_dead(&tmp.path().join("nope"), "bl-x").is_err());
}
#[test]
fn dead_balls_enumerates_every_dead_ball_newest_first() {
let s = git_store();
s.create("bl-a", &task("A", 1), 1).retire("bl-a", "close", 10);
s.create("bl-b", &task("B", 2), 2).retire("bl-b", "drop", 20);
let live = Catalog::load(s.dir()).unwrap();
let dead = dead_balls(s.dir(), &live).unwrap();
let ids: Vec<&str> = dead.iter().map(|d| d.id.as_str()).collect();
assert_eq!(ids, ["bl-b", "bl-a"]); }
#[test]
fn dead_balls_excludes_an_id_that_is_live_again() {
let s = git_store();
s.create("bl-x", &task("X", 1), 1).retire("bl-x", "close", 2);
s.create("bl-x", &task("X-again", 3), 3);
let live = Catalog::load(s.dir()).unwrap();
let dead = dead_balls(s.dir(), &live).unwrap();
assert!(dead.is_empty()); }
#[test]
fn dead_balls_attributes_every_path_in_one_commit_to_that_commit() {
let s = git_store();
let mut a = task("Child A", 1);
a.body = "first".into();
let mut b = task("Child B", 2);
b.body = "second".into();
s.create("bl-a", &a, 1).create("bl-b", &b, 2).retire_all(&["bl-a", "bl-b"], "close", 77);
let live = Catalog::load(s.dir()).unwrap();
let dead = dead_balls(s.dir(), &live).unwrap();
let got: Vec<(&str, &str, i64)> =
dead.iter().map(|d| (d.id.as_str(), d.task.body.as_str(), d.retired_at)).collect();
assert_eq!(got, [("bl-a", "first", 77), ("bl-b", "second", 77)]);
}
#[test]
fn dead_balls_frames_the_batch_on_bytes_not_characters() {
let s = git_store();
let mut wide = task("Accented", 1);
wide.body = "café — naïve 日本語 🜂".into();
let mut plain = task("Plain", 2);
plain.body = "ascii".into();
s.create("bl-wide", &wide, 1).retire("bl-wide", "close", 10);
s.create("bl-plain", &plain, 2).retire("bl-plain", "close", 20);
let live = Catalog::load(s.dir()).unwrap();
let dead = dead_balls(s.dir(), &live).unwrap();
assert_eq!(dead[0].task.body, "ascii"); assert_eq!(dead[1].task.body, "café — naïve 日本語 🜂");
assert_eq!(dead[1].task.title, "Accented"); }
#[test]
fn dead_balls_surfaces_a_corrupt_historical_file_as_an_error() {
let s = git_store();
s.create_raw("bl-bad", "not valid frontmatter", 1).retire("bl-bad", "close", 2);
let live = Catalog::load(s.dir()).unwrap();
assert!(dead_balls(s.dir(), &live).is_err());
}
#[test]
fn dead_balls_is_empty_when_nothing_was_ever_deleted() {
let s = git_store();
s.create("bl-live", &task("Alive", 1), 1);
let live = Catalog::load(s.dir()).unwrap();
assert!(dead_balls(s.dir(), &live).unwrap().is_empty());
}
#[test]
fn dead_balls_keeps_a_deletion_that_shares_its_commit_with_a_similar_add() {
let s = git_store();
let mut gone = task("Retired", 1);
gone.body = "the plan\nstep one\nstep two\nstep three\n".into();
let mut born = task("Successor", 2);
born.body = gone.body.clone();
s.create("bl-gone", &gone, 1).retire_and_create("bl-gone", "bl-new", &born, 50);
let live = Catalog::load(s.dir()).unwrap();
let dead = dead_balls(s.dir(), &live).unwrap();
let ids: Vec<&str> = dead.iter().map(|d| d.id.as_str()).collect();
assert_eq!(ids, ["bl-gone"]);
assert_eq!(dead[0].task.title, "Retired");
}
#[test]
fn dead_balls_dedupes_a_twice_deleted_id_to_its_newest() {
let s = git_store();
s.create("bl-d", &task("D1", 1), 1).retire("bl-d", "close", 2);
s.create("bl-d", &task("D2", 3), 3).retire("bl-d", "drop", 4);
let live = Catalog::load(s.dir()).unwrap();
let dead = dead_balls(s.dir(), &live).unwrap();
assert_eq!(dead.len(), 1); assert_eq!(dead[0].task.title, "D2");
}