use gwm::config::{Config, SidebarPosition, TuiOpenMode};
use gwm::config_cli::{set_array_at, set_string_at, set_value_at, unset_at};
use gwm::tui::keymap::{Action, ChordResolution, KeyStroke};
use gwm::tui::modal_keymap::{KeyContext, ModalAction};
use std::path::Path;
fn load(repo: &Path, global: Option<&Path>) -> Config {
Config::load_layered(repo, global).expect("config should load")
}
#[test]
fn set_value_at_persists_repo_layer_and_round_trips_typed() {
let repo = tempfile::tempdir().unwrap();
let gwm_toml = repo.path().join(".gwm.toml");
set_value_at(&gwm_toml, "theme.preset", "gruvbox").unwrap();
set_value_at(&gwm_toml, "tui.sidebar_position", "left").unwrap();
let cfg = load(repo.path(), None);
assert_eq!(cfg.theme.preset.as_deref(), Some("gruvbox"));
assert_eq!(cfg.tui.sidebar_position, SidebarPosition::Left);
}
#[test]
fn set_value_at_creates_nested_tables_from_an_empty_file() {
let repo = tempfile::tempdir().unwrap();
let gwm_toml = repo.path().join(".gwm.toml");
set_value_at(&gwm_toml, "tui.open.mode", "editor").unwrap();
let cfg = load(repo.path(), None);
assert_eq!(cfg.tui.open.mode, TuiOpenMode::Editor);
}
#[test]
fn set_value_at_writes_global_layer_and_creates_parent_dir() {
let repo = tempfile::tempdir().unwrap();
let home = tempfile::tempdir().unwrap();
let global = home.path().join("gwm").join("config.toml");
assert!(!global.parent().unwrap().exists());
set_value_at(&global, "tui.confirm_countdown_secs", "3").unwrap();
assert!(global.exists(), "writer must create the global file + its parent dir");
let cfg = load(repo.path(), Some(&global));
assert_eq!(cfg.tui.confirm_countdown_secs, 3);
}
#[test]
fn set_string_at_preserves_numeric_looking_text_as_a_string() {
let repo = tempfile::tempdir().unwrap();
let gwm_toml = repo.path().join(".gwm.toml");
set_string_at(&gwm_toml, "worktree.base", "123").unwrap();
let raw = std::fs::read_to_string(&gwm_toml).unwrap();
assert!(
raw.contains("base = \"123\""),
"value must be quoted as a string: {raw}"
);
let cfg = load(repo.path(), None);
assert_eq!(cfg.worktree.base, "123");
}
#[test]
fn an_invalid_write_does_not_clobber_the_existing_file() {
let repo = tempfile::tempdir().unwrap();
let gwm_toml = repo.path().join(".gwm.toml");
std::fs::write(&gwm_toml, "[tui]\nconfirm_countdown_secs = 4\n").unwrap();
let result = set_string_at(&gwm_toml, "tui.confirm_countdown_secs", "abc");
assert!(result.is_err(), "writing a non-numeric value to a u32 field must fail");
let cfg = load(repo.path(), None);
assert_eq!(
cfg.tui.confirm_countdown_secs, 4,
"the prior valid file must survive a rejected write"
);
}
#[test]
fn editing_an_already_invalid_file_still_writes_the_change() {
let repo = tempfile::tempdir().unwrap();
let gwm_toml = repo.path().join(".gwm.toml");
std::fs::write(&gwm_toml, "[tui]\nconfirm_countdown_secs = \"abc\"\n").unwrap();
let result = set_string_at(&gwm_toml, "theme.preset", "gruvbox");
assert!(result.is_err(), "the pre-existing invalid value is still surfaced");
let raw = std::fs::read_to_string(&gwm_toml).unwrap();
assert!(
raw.contains("preset = \"gruvbox\""),
"the unrelated edit must still be written to an already-invalid file: {raw}"
);
}
#[test]
fn set_array_at_writes_a_global_keymap_array_and_round_trips() {
let repo = tempfile::tempdir().unwrap();
let gwm_toml = repo.path().join(".gwm.toml");
set_array_at(&gwm_toml, "tui.keys.quit", &["Q".to_string()]).unwrap();
let raw = std::fs::read_to_string(&gwm_toml).unwrap();
assert!(raw.contains("quit = [\"Q\"]"), "must write a TOML array: {raw}");
let cfg = load(repo.path(), None);
let km = cfg.tui.keys.resolved_keymap().expect("keymap resolves");
let q = KeyStroke::new(
crossterm::event::KeyCode::Char('Q'),
crossterm::event::KeyModifiers::empty(),
);
assert_eq!(km.lookup(&[q]), ChordResolution::Matched(Action::Quit));
}
#[test]
fn set_array_at_writes_a_modal_verb_into_its_nested_table() {
let repo = tempfile::tempdir().unwrap();
let gwm_toml = repo.path().join(".gwm.toml");
set_array_at(&gwm_toml, "tui.keys.modal.confirm.confirm", &["o".to_string()]).unwrap();
let cfg = load(repo.path(), None);
let mk = cfg.tui.keys.resolved_modal_keymap().expect("modal keymap resolves");
let o = KeyStroke::new(
crossterm::event::KeyCode::Char('o'),
crossterm::event::KeyModifiers::empty(),
);
assert_eq!(mk.resolve(KeyContext::Confirm, &o), Some(ModalAction::ConfirmConfirm));
}
#[test]
fn set_array_at_can_unbind_with_an_empty_array() {
let repo = tempfile::tempdir().unwrap();
let gwm_toml = repo.path().join(".gwm.toml");
set_array_at(&gwm_toml, "tui.keys.modal.confirm.confirm", &[]).unwrap();
let cfg = load(repo.path(), None);
let mk = cfg.tui.keys.resolved_modal_keymap().expect("modal keymap resolves");
let y = KeyStroke::new(
crossterm::event::KeyCode::Char('y'),
crossterm::event::KeyModifiers::empty(),
);
assert_eq!(
mk.resolve(KeyContext::Confirm, &y),
None,
"the default `y` is gone once `confirm` is unbound"
);
}
#[test]
fn set_array_at_rejects_a_prefix_collision_and_leaves_the_file_untouched() {
let repo = tempfile::tempdir().unwrap();
let gwm_toml = repo.path().join(".gwm.toml");
let result = set_array_at(&gwm_toml, "tui.keys.refresh", &["g".to_string()]);
assert!(result.is_err(), "a prefix collision must be rejected");
assert!(!gwm_toml.exists(), "a rejected write must not create the file");
}
#[test]
fn unset_at_removes_a_key_and_tolerates_absent_targets() {
let repo = tempfile::tempdir().unwrap();
let gwm_toml = repo.path().join(".gwm.toml");
set_array_at(&gwm_toml, "tui.keys.open_menu", &["B".to_string()]).unwrap();
unset_at(&gwm_toml, "tui.keys.open_menu").unwrap();
let raw = std::fs::read_to_string(&gwm_toml).unwrap();
assert!(!raw.contains("open_menu"), "key removed: {raw}");
unset_at(&gwm_toml, "tui.keys.does_not_exist").unwrap();
unset_at(&repo.path().join("nope.toml"), "tui.keys.x").unwrap();
}
#[test]
fn set_value_at_preserves_other_keys_in_the_file() {
let repo = tempfile::tempdir().unwrap();
let gwm_toml = repo.path().join(".gwm.toml");
std::fs::write(
&gwm_toml,
"[worktree]\nbase = \"/tmp/keepme\"\n\n[tui]\nconfirm_countdown_secs = 4\n",
)
.unwrap();
set_value_at(&gwm_toml, "tui.sidebar_position", "left").unwrap();
let cfg = load(repo.path(), None);
assert_eq!(cfg.tui.sidebar_position, SidebarPosition::Left);
assert_eq!(cfg.tui.confirm_countdown_secs, 4, "untouched key must survive");
assert_eq!(cfg.worktree.base, "/tmp/keepme", "untouched table must survive");
}