#[allow(clippy::wildcard_imports)]
use super::super::*;
#[allow(clippy::wildcard_imports)]
use super::helpers::*;
use tempfile::TempDir;
#[test]
fn quit_on_q() {
let (_tmp, mut app) = make_app();
let action = app.handle_key(press(KeyCode::Char('q'))).unwrap();
assert!(app.quit);
assert!(matches!(action, Action::Quit));
}
#[test]
fn quit_on_esc() {
let (_tmp, mut app) = make_app();
let _ = app.handle_key(press(KeyCode::Esc)).unwrap();
assert!(app.quit);
}
#[test]
fn the_confirm_chord_also_accepts_the_edit_warning() {
let (_tmp, mut app) = make_app();
let dir = app.store.issues_dir.clone();
write_issue_with_custom_key(&dir, "2026-01-01-a.md", "build_id");
app.after_editor("2026-01-01-a.md");
let _ = app.handle_key(press(KeyCode::Char('y'))).unwrap();
assert!(matches!(app.mode, Mode::Board));
assert_eq!(app.store.config.custom_attributes.keys, vec!["build_id"]);
}
#[test]
fn declaring_the_same_key_again_does_not_duplicate_it() {
let (tmp, mut app) = make_app();
let dir = app.store.issues_dir.clone();
write_issue_with_custom_key(&dir, "2026-01-01-a.md", "build_id");
app.after_editor("2026-01-01-a.md");
let _ = app.handle_key(press(KeyCode::Char('a'))).unwrap();
for key in ["build_id", "BUILD_ID"] {
app.mode = repair_mode("unknown_key: already handled", Some(key));
let _ = app.handle_key(press(KeyCode::Char('a'))).unwrap();
}
let reloaded = Config::load_with_warning(&tmp.path().join("tix.toml"), |_| {}).unwrap();
assert_eq!(
reloaded.custom_attributes.keys,
vec!["build_id".to_string()]
);
}
#[test]
fn key_release_ignored() {
let (_tmp, mut app) = make_app();
let key = KeyEvent {
code: KeyCode::Char('q'),
modifiers: crossterm::event::KeyModifiers::NONE,
kind: KeyEventKind::Release,
state: crossterm::event::KeyEventState::NONE,
};
let _ = app.handle_key(key).unwrap();
assert!(!app.quit);
}
#[test]
fn f4_a_disclosure_is_composed_with_the_confirmation_not_substituted() {
let (_tmp, mut app) = make_app();
app.status_msg = Some("Moved #1 to in progress".to_string());
app.note_disclosure("legacy-1.md: this write removed the retired `links:` key.".into());
let status = app.status_msg.as_deref().unwrap();
assert!(
status.contains("Moved #1 to in progress"),
"the confirmation must survive: {status}"
);
assert!(
status.contains("retired `links:` key"),
"and the disclosure must be shown: {status}"
);
let disclosure_at = status.find("retired").expect("disclosure is present");
let confirmation_at = status.find("Moved #1").expect("confirmation is present");
assert!(
disclosure_at < confirmation_at,
"the disclosure leads, so its actionable tail survives truncation: {status}"
);
let (_tmp2, mut app2) = make_app();
app2.note_disclosure("alone".into());
assert_eq!(app2.status_msg.as_deref(), Some("alone"));
}
#[test]
fn run_child_tix_refuses_a_path_that_is_not_a_repository() {
let tmp = TempDir::new().unwrap();
let missing = tmp.path().join("absent");
let err = run_child_tix(&missing, tmp.path()).unwrap_err().to_string();
assert!(err.contains("not a directory"), "got {err}");
let no_config = tmp.path().join("plain");
std::fs::create_dir_all(&no_config).unwrap();
let err = run_child_tix(&no_config, tmp.path())
.unwrap_err()
.to_string();
assert!(err.contains("no tix.toml"), "got {err}");
}
#[test]
fn function_keys_switch_top_level_modes() {
let (_tmp, mut app) = make_shell_app();
assert_eq!(app.top_mode, TopMode::Board);
app.handle_key(press(KeyCode::F(1))).unwrap();
assert!(
matches!(app.mode, Mode::Form),
"F1 opens the New Issue mode's own content, got {:?}",
app.mode
);
assert_eq!(app.top_mode, TopMode::NewIssue);
app.handle_key(press(KeyCode::F(4))).unwrap();
assert!(
matches!(app.mode, Mode::Cycle),
"F4 opens the Cycles mode, got {:?}",
app.mode
);
assert_eq!(app.top_mode, TopMode::Cycle);
app.handle_key(press(KeyCode::F(5))).unwrap();
assert!(
matches!(app.mode, Mode::Settings),
"F5 opens the Milestones mode on the borrowed Config surface, got {:?}",
app.mode
);
assert_eq!(app.top_mode, TopMode::Milestones);
assert!(app.milestone_mode);
assert_eq!(
app.settings.as_ref().map(|s| s.section),
Some(ConfigSection::Milestones)
);
app.handle_key(press(KeyCode::F(12))).unwrap();
assert!(matches!(app.mode, Mode::Settings));
assert_eq!(app.top_mode, TopMode::Config);
assert!(!app.milestone_mode, "F12 is Config, not Milestones");
app.handle_key(press(KeyCode::F(2))).unwrap();
assert!(
matches!(app.mode, Mode::TriageInbox),
"F2 must open the triage inbox, got {:?}",
app.mode
);
assert_eq!(app.top_mode, TopMode::Triage);
app.handle_key(press(KeyCode::F(3))).unwrap();
assert!(matches!(app.mode, Mode::Board));
assert_eq!(app.top_mode, TopMode::Board);
}
#[test]
fn unbound_key_takes_the_f12_sentinel_path_unchanged() {
let (_tmp, mut app) = make_shell_app();
app.handle_key(press(KeyCode::F(9))).unwrap();
assert!(matches!(app.mode, Mode::Board));
assert_eq!(app.top_mode, TopMode::Board);
assert_eq!(
app.bindings.action(
InputContext::Shell,
KeyEvent::new(KeyCode::F(9), KeyModifiers::NONE)
),
None
);
}
#[test]
fn letter_keys_still_open_each_mode() {
let (_tmp, mut app) = make_shell_app();
app.handle_key(press(KeyCode::Char('m'))).unwrap();
assert!(matches!(app.mode, Mode::PlanningManagement));
assert_eq!(app.top_mode, TopMode::Cycle);
app.handle_key(press(KeyCode::Esc)).unwrap();
app.handle_key(press(KeyCode::Char('t'))).unwrap();
assert!(matches!(app.mode, Mode::TriageInbox));
assert_eq!(app.top_mode, TopMode::Triage);
}
#[test]
fn activating_a_branch_reaches_its_target() {
let (_tmp, mut app) = make_branchy_app();
app.activate_branch(&Action::SelectBoard("release".into()))
.unwrap();
assert!(matches!(app.mode, Mode::Board));
assert_eq!(app.active_board_name(), "release");
assert_eq!(app.top_mode, TopMode::Board);
let (_tmp, mut app) = make_cycle_app();
app.activate_branch(&Action::SelectCycle("Ongoing".into()))
.unwrap();
assert!(matches!(app.mode, Mode::Cycle));
assert_eq!(app.top_mode, TopMode::Cycle);
assert_eq!(
app.cycle_state.as_ref().map(|s| s.selected),
Some(1),
"the cursor lands on Ongoing"
);
let (_tmp, mut app) = make_templated_app();
app.activate_branch(&Action::NewFromTemplate("bug".into()))
.unwrap();
assert!(matches!(app.mode, Mode::Form));
assert_eq!(app.top_mode, TopMode::NewIssue);
assert_eq!(app.form.as_ref().map(|f| f.template.as_str()), Some("bug"));
}