use crate::binding::{work_worktree_path, workspace_path};
use crate::projects::join::JoinState;
use crate::start::goal::{compose_prepared, prefill, target_binding};
use crate::start::identity::mint_conversation;
use crate::start::{BallSpec, Payload, StartInputs, parse_ball_stamp, preview};
use std::path::{Path, PathBuf};
const YOG: &str = "/yog";
const BALLS: &str = "/balls";
const HOME: &str = "/home/op";
const PROJ: &str = "/proj";
fn existing_ball() -> Payload {
Payload::Ball {
project: "proj".to_owned(),
ball: BallSpec::Existing {
id: "bl-1".to_owned(),
title: "T".to_owned(),
body: "B".to_owned(),
join: JoinState::ReadyStartable,
tags: Vec::new(),
},
}
}
fn new_ball() -> Payload {
Payload::Ball {
project: "proj".to_owned(),
ball: BallSpec::New {
title: "Fresh".to_owned(),
body: "do".to_owned(),
},
}
}
#[test]
fn prefill_is_empty_for_bare() {
assert_eq!(prefill(&Payload::Bare), "");
}
#[test]
fn prefill_names_the_path_verbatim_on_line_one() {
let g = prefill(&Payload::Path {
dir: PathBuf::from("/work/here"),
});
assert_eq!(
g,
"Working directory: /work/here\nDo all work there, by absolute path. Do not rely on the current directory.",
);
assert_eq!(g.lines().next(), Some("Working directory: /work/here"));
assert!(!g.contains("workspace"));
}
#[test]
fn prefill_for_a_ball_is_the_header_and_body_with_no_location_prose() {
let wt = work_worktree_path(Path::new(BALLS), Path::new(PROJ), "bl-1", None);
let g = prefill(&existing_ball());
assert_eq!(g, "Ball bl-1: T\n\nB");
assert!(!g.contains(&wt.display().to_string()));
assert!(!g.contains(PROJ));
assert!(!g.contains("worktree"));
assert!(!g.contains("absolute path"));
}
#[test]
fn prefill_for_a_new_ball_is_title_and_body_only() {
assert_eq!(prefill(&new_ball()), "Ball (new): Fresh\n\ndo");
}
#[test]
fn parse_ball_stamp_is_the_inverse_of_the_composed_header() {
let modern = prefill(&existing_ball());
assert_eq!(parse_ball_stamp(&modern).as_deref(), Some("bl-1"));
let legacy = format!("You are cobalt-gecko.\n\n{modern}");
assert_eq!(parse_ball_stamp(&legacy).as_deref(), Some("bl-1"));
}
#[test]
fn parse_ball_stamp_rejects_non_ball_goals_and_malformed_headers() {
assert_eq!(parse_ball_stamp("You are x.\n\nfree prose"), None);
assert_eq!(parse_ball_stamp("Ball : no id"), None);
assert_eq!(parse_ball_stamp("Ball two words: nope"), None);
assert_eq!(
parse_ball_stamp("You are y.\n\nBall bz-9: T\n\nbody").as_deref(),
Some("bz-9"),
);
}
#[test]
fn target_binding_is_the_per_rung_work_target() {
let wt = work_worktree_path(Path::new(BALLS), Path::new(PROJ), "bl-1", None);
assert_eq!(target_binding(&Payload::Bare, None), None);
assert_eq!(
target_binding(
&Payload::Path {
dir: PathBuf::from("/d")
},
None,
),
Some(PathBuf::from("/d")),
);
assert_eq!(target_binding(&existing_ball(), Some(&wt)), Some(wt));
assert_eq!(target_binding(&new_ball(), None), None);
}
#[test]
fn compose_prepared_derives_the_fire_params() {
let inputs = StartInputs {
workspace: workspace_path(Path::new(YOG), "cobalt-gecko"),
repo: Some(PathBuf::from(PROJ)),
payload: Payload::Bare,
home: PathBuf::from(HOME),
yog_data_root: PathBuf::from(YOG),
balls_state_root: PathBuf::from(BALLS),
conversation_names: Vec::new(),
};
let p = compose_prepared(&inputs, None);
assert_eq!(p.workspace, "cobalt-gecko");
assert_eq!(p.binding, None, "the bare rung binds no work target");
assert_eq!(p.goal, "");
let wt = work_worktree_path(Path::new(BALLS), Path::new(PROJ), "bl-1", None);
let ball = StartInputs {
payload: existing_ball(),
..inputs.clone()
};
let bound = compose_prepared(&ball, Some(&wt));
assert_eq!(bound.binding, Some(wt));
let foreign = StartInputs {
workspace: PathBuf::from("/litany/workspaces/20260101T-aa"),
..inputs
};
assert_eq!(compose_prepared(&foreign, None).workspace, "20260101T-aa");
}
#[test]
fn preview_pairs_the_predicted_identity_with_the_prefill() {
let w = super::World::new();
let mut inputs = w.inputs(
"cobalt-gecko",
Payload::Path {
dir: PathBuf::from("/d"),
},
);
let taken = mint_conversation(&[], &super::rng()).unwrap();
inputs.conversation_names = vec![taken.clone()];
let c = preview(&inputs, &super::rng());
assert_ne!(c.preview, format!("will be named {taken}"));
assert!(c.preview.starts_with("will be named "));
assert!(
!c.preview.contains("cobalt-gecko"),
"the workspace name is never the previewed identity (§3.3, bl-df65)"
);
assert!(c.prefill.contains("/d"));
}
#[test]
fn preview_predicts_a_minted_name() {
let w = super::World::new();
let inputs = w.inputs(crate::names::DEFAULT_NAME, Payload::Bare);
let c = preview(&inputs, &super::rng());
assert_eq!(
c.preview,
format!(
"will be named {}",
mint_conversation(&[], &super::rng()).unwrap()
)
);
assert_eq!(c.prefill, "", "bare has no prefill");
}