use super::TEMPLATE_PROVIDERS;
use crate::model_pick::{
ConfigPoint, RETARGET_EXIT, RETARGET_HOVER, birth_row, conversation_row, row_role,
};
fn resolved() -> ConfigPoint {
point("1a2b3c4dfeed", "1a2b3c4d", TEMPLATE_PROVIDERS)
}
fn point(oid: &str, short_oid: &str, providers_yaml: &str) -> ConfigPoint {
ConfigPoint {
oid: oid.to_string(),
short_oid: short_oid.to_string(),
providers_yaml: providers_yaml.to_string(),
}
}
fn advanced(model: &str) -> ConfigPoint {
point(
"9f8e7d6cbeef",
"9f8e7d6c",
&TEMPLATE_PROVIDERS.replace("model: gpt-5.4\n", &format!("model: {model}\n")),
)
}
#[test]
fn the_row_is_the_pair_and_the_resolved_commit_rides_the_hover() {
let point = resolved();
let row = conversation_row(&point, &point, "worker");
assert_eq!(row.provider, "codex");
assert_eq!(row.model, "gpt-5.4");
assert!(!row.is_apart());
assert!(row.hover.contains("1a2b3c4d"));
assert!(row.hover.contains("at its next step"));
assert!(
!row.hover.contains("frozen"),
"the hover may not promise a freeze the engine stopped keeping: {}",
row.hover
);
}
#[test]
fn a_conversation_following_the_lineage_wears_the_advanced_pair_with_no_clause() {
let tip = advanced("gpt-5.6-sol");
let row = conversation_row(&tip, &tip, "worker");
assert_eq!(row.model, "gpt-5.6-sol");
assert!(!row.is_apart());
assert!(row.apart.is_none());
}
#[test]
fn a_conversation_resolving_another_commit_names_its_own_pair_beside_it() {
let row = conversation_row(&resolved(), &advanced("gpt-5.6-sol"), "worker");
assert_eq!(
row.apart.as_deref(),
Some(
"this conversation resolves codex · gpt-5.4 at 1a2b3c4d, not this workspace's lineage"
)
);
assert!(row.is_apart());
}
#[test]
fn a_lineage_tip_carrying_the_same_model_is_still_apart() {
let row = conversation_row(&resolved(), &advanced("gpt-5.4"), "worker");
assert!(row.is_apart());
assert!(
row.apart
.unwrap_or_default()
.contains("gpt-5.4 at 1a2b3c4d")
);
}
#[test]
fn a_conversation_on_this_workspaces_lineage_carries_no_clause() {
let point = resolved();
assert!(conversation_row(&point, &point, "worker").apart.is_none());
}
#[test]
fn a_row_for_an_undeclared_role_paints_the_absence() {
let row = conversation_row(&resolved(), &resolved(), "nonesuch");
assert_eq!(row.provider, "(none)");
assert_eq!(row.model, "(none)");
let empty = point("bbb", "abc", "");
let row = conversation_row(&empty, &empty, "worker");
assert_eq!(row.model, "(none)");
}
#[test]
fn the_one_exit_settles_this_conversation_and_keeps_it() {
assert!(RETARGET_EXIT.contains("settle this conversation"));
assert!(RETARGET_EXIT.contains("config lineage"));
assert!(!RETARGET_EXIT.contains("new conversation"));
assert!(RETARGET_HOVER.contains("litany retarget"));
assert!(RETARGET_HOVER.contains("reaches it on its own"));
assert!(RETARGET_HOVER.contains("next step"));
assert!(RETARGET_HOVER.contains("keeps every message"));
assert!(RETARGET_HOVER.contains("/retarget"));
}
#[test]
fn the_birth_row_names_the_pair_and_the_branch_head_it_forks() {
let row = birth_row(&resolved(), "worker", "default");
assert_eq!(
(row.provider.as_str(), row.model.as_str()),
("codex", "gpt-5.4")
);
assert!(row.apart.is_none());
assert!(row.hover.contains("forks the head of config/default"));
assert!(row.hover.contains("follows that lineage"));
assert!(row.hover.contains("1a2b3c4d"));
assert!(row.hover.contains("no per-conversation config"));
}
#[test]
fn a_birth_tip_with_no_worker_role_still_paints_the_row() {
let none = point(
"aaa",
"abc",
"roles:\n compactor:\n provider: p\n model: m\n",
);
assert_eq!(birth_row(&none, "worker", "default").provider, "(none)");
}
#[test]
fn the_bare_row_reports_the_worker_role() {
assert_eq!(row_role(None), "worker");
assert_eq!(row_role(Some("compactor")), "compactor");
}