use super::{empty_model, model, model_focused, set_list, world};
use crate::binding::workspace_path;
use crate::names::DEFAULT_NAME;
use crate::projects::join::JoinState;
use crate::start::{BallSpec, Payload};
use std::fs;
#[test]
fn internal_clones_never_reach_the_project_surface() {
let w = world();
let internal = w
.roots
.balls_clones
.parent()
.unwrap()
.join("plugins/bl-delivery/home/u/p/bl-x");
let enc = internal.to_string_lossy().replace('/', "%2F");
fs::create_dir_all(w.roots.balls_clones.join(enc)).unwrap();
let (_c, mut m) = model(&w);
assert_eq!(
m.project_paths(),
vec![w.project.clone()],
"the internal clone is filtered out of the project surface"
);
m.tick();
assert_eq!(m.project_paths(), vec![w.project.clone()], "and stays out");
}
#[test]
fn start_inputs_expose_the_ball_payload_and_roots() {
let w = world();
set_list(
&w,
r#"[{"id":"bl-rdy","title":"Go","body":"do it"},{"id":"bl-me","claimant":"me"}]"#,
);
let (_c, m) = model(&w);
let cards = m.startable();
assert_eq!(cards.len(), 1);
let Payload::Ball {
project,
ball: BallSpec::Existing { id, body, join, .. },
} = &cards[0].payload
else {
panic!("existing ball payload expected");
};
assert_eq!(id, "bl-rdy");
assert_eq!(body, "do it");
assert_eq!(*join, JoinState::ReadyStartable);
assert_eq!(project, &w.project);
assert_eq!(cards[0].home.as_path(), w.roots.home.as_path());
assert_eq!(
cards[0].balls_state_root.as_path(),
w.roots.balls_clones.parent().unwrap()
);
let nb = m.new_ball_inputs(&w.project, "T", "B");
assert!(matches!(
nb.payload,
Payload::Ball {
ball: BallSpec::New { .. },
..
}
));
assert_eq!(
nb.workspace, cards[0].workspace,
"one resolved target per instance"
);
assert_eq!(m.yog_data_root(), w.roots.yog_data.as_path());
assert_eq!(m.project_paths(), vec![w.project.clone()]);
}
#[test]
fn bare_target_is_the_focused_workspace_named_or_foreign() {
let w = world();
let (_c, m) = model_focused(&w, &w.ws_cobalt);
assert_eq!(m.start_bare_inputs().workspace, w.ws_cobalt);
let foreign = w.roots.lernie_data.join("workspaces/foreign");
let (_c2, mf) = model_focused(&w, &foreign);
assert_eq!(mf.start_bare_inputs().workspace, foreign);
}
#[test]
fn bare_target_is_the_default_name_without_a_focus() {
let (_root, m) = empty_model();
let inputs = m.start_bare_inputs();
assert_eq!(
inputs.workspace,
workspace_path(m.yog_data_root(), DEFAULT_NAME)
);
assert!(inputs.workspace.ends_with("workspaces/home"));
}
#[test]
fn new_workspace_inputs_take_the_typed_name_over_any_focus() {
let w = world();
let (_c, mut m) = model(&w);
m.focus_workspace(&w.ws_cobalt);
let inputs = m.new_workspace_inputs("ops");
assert_eq!(
inputs.workspace,
workspace_path(&w.roots.yog_data, "ops"),
"the raise names its own workspace, not the focused one"
);
assert_eq!(m.validate_workspace_name(" ops "), Ok("ops".to_owned()));
assert!(m.validate_workspace_name("Ops!").is_err());
assert!(m.validate_workspace_name("unknown").is_err());
assert!(
m.validate_workspace_name("cobalt").is_err(),
"an existing leaf under any of the three roots is refused (§3.1)"
);
}
#[test]
fn start_path_inputs_carries_the_path_payload_at_the_focused_target() {
let w = world();
let (_c, mut m) = model(&w);
m.focus_workspace(&w.ws_cobalt);
let inputs = m.start_path_inputs(std::path::Path::new("/work/here"));
assert_eq!(inputs.workspace, w.ws_cobalt);
let Payload::Path { dir } = inputs.payload else {
panic!("path payload");
};
assert_eq!(dir, std::path::PathBuf::from("/work/here"));
}
#[test]
fn empty_project_hint_only_with_zero_projects() {
let w = world();
let (_c, m) = model(&w);
assert!(m.empty_project_hint().is_none(), "a project is present");
let (_root, empty) = empty_model();
assert_eq!(
empty.empty_project_hint(),
Some(crate::app::balls::EmptyHint {
lead: "No projects yet — add one with:".to_owned(),
command: "yog exec bl prime".to_owned(),
})
);
}
#[test]
fn workspace_names_lists_the_named_workspaces_excluding_foreign() {
let w = world();
fs::create_dir_all(w.roots.lernie_data.join("workspaces/adhoc/repo.git")).unwrap();
let (_c, m) = model(&w);
let mut names = m.workspace_names();
names.sort();
assert_eq!(
names,
vec!["cobalt".to_owned(), "spare".to_owned()],
"foreign 'adhoc' excluded",
);
}
#[test]
fn move_targets_are_the_named_workspaces_minus_the_one_holding_the_ball() {
let w = world();
let (_c, m) = model(&w);
assert_eq!(m.move_targets("cobalt"), vec!["spare".to_owned()]);
let mut all = m.move_targets("nobody");
all.sort();
assert_eq!(all, vec!["cobalt".to_owned(), "spare".to_owned()]);
}
#[test]
fn focused_ws_name_is_the_focused_leaf_else_none() {
let w = world();
let (_c, mut m) = model(&w);
m.focus_workspace(&w.ws_cobalt);
assert_eq!(m.focused_ws_name(), Some("cobalt".to_owned()));
let (_root, empty) = empty_model();
assert_eq!(empty.focused_ws_name(), None, "no focus ⇒ no target");
}
#[test]
fn resumable_targets_the_bound_balls_own_workspace() {
let w = world();
let (_c, m) = model(&w);
let cards = m.resumable();
assert_eq!(cards.len(), 1, "only bl-work (Bound under cobalt)");
assert_eq!(cards[0].workspace, w.ws_cobalt);
let Payload::Ball {
ball: BallSpec::Existing { id, join, .. },
..
} = &cards[0].payload
else {
panic!("existing-ball payload");
};
assert_eq!(id, "bl-work");
assert_eq!(*join, JoinState::Bound);
}