use {super::*, reovim_kernel::api::v1::ModuleId};
const TEST_MODULE: ModuleId = ModuleId::new("test");
fn test_mode() -> ModeId {
ModeId::with_discriminant(TEST_MODULE, "NORMAL", 0)
}
fn test_command(name: &'static str) -> CommandId {
CommandId::new(TEST_MODULE, name)
}
#[test]
fn test_keymap_registry_new() {
let registry = KeymapRegistry::new();
assert!(registry.is_empty());
assert_eq!(registry.total_bindings(), 0);
}
#[test]
fn test_keymap_registry_register() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
let keys = KeySequence::parse("j").unwrap();
let cmd = test_command("cursor-down");
registry.register_at_layer(BindingLayer::Policy, &mode, keys, cmd, "", None);
assert_eq!(registry.binding_count(&mode), 1);
assert!(!registry.is_empty());
}
#[test]
fn test_keymap_registry_lookup_found() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
let cmd = test_command("cursor-down");
registry.register_str(&mode, "j", cmd.clone());
let keys = KeySequence::parse("j").unwrap();
let result = registry.lookup(&mode, &keys);
assert!(result.is_found());
assert_eq!(result.command_id(), Some(&cmd));
}
#[test]
fn test_keymap_registry_lookup_prefix() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
registry.register_str(&mode, "gg", test_command("goto-top"));
let g = KeySequence::parse("g").unwrap();
let result = registry.lookup(&mode, &g);
assert!(result.is_prefix());
}
#[test]
fn test_keymap_registry_multi_key_sequence() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
registry.register_str(&mode, "g", test_command("goto"));
registry.register_str(&mode, "gg", test_command("goto-top"));
let g = KeySequence::parse("g").unwrap();
let result = registry.lookup(&mode, &g);
assert!(result.is_found());
assert_eq!(result.command_id(), Some(&test_command("goto")));
let state = registry.query(&mode, &g);
assert_eq!(
state,
KeyLookupState::ExactWithLonger {
exact: test_command("goto")
}
);
let gg = KeySequence::parse("gg").unwrap();
let result = registry.lookup(&mode, &gg);
assert!(result.is_found());
}
#[test]
fn test_layer_override() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
let keys = KeySequence::parse("d").unwrap();
registry.register_at_layer(
BindingLayer::Policy,
&mode,
keys.clone(),
test_command("delete"),
"",
None,
);
registry.register_at_layer(
BindingLayer::User,
&mode,
keys.clone(),
test_command("custom-delete"),
"",
None,
);
let binding = registry.get_binding(&mode, &keys);
assert_eq!(binding, Some(test_command("custom-delete")));
}
#[test]
fn test_unregister_for_module() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
let owner = ModuleId::new("my-module");
let j = KeySequence::parse("j").unwrap();
let k = KeySequence::parse("k").unwrap();
registry.register_at_layer_for_module(
&mode,
j.clone(),
BindingInfo::from_command(test_command("down"), BindingLayer::Policy),
owner.clone(),
);
registry.register_at_layer_for_module(
&mode,
k,
BindingInfo::from_command(test_command("up"), BindingLayer::Policy),
owner.clone(),
);
let l = KeySequence::parse("l").unwrap();
registry.register_at_layer(
BindingLayer::Policy,
&mode,
l.clone(),
test_command("right"),
"",
None,
);
assert_eq!(registry.binding_count(&mode), 3);
let removed = registry.unregister_for_module(&owner);
assert_eq!(removed, 2);
assert_eq!(registry.binding_count(&mode), 1);
assert!(registry.lookup(&mode, &j).is_not_found());
assert!(registry.lookup(&mode, &l).is_found());
}
#[test]
fn test_clear_layer() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
let j = KeySequence::parse("j").unwrap();
registry.register_at_layer(
BindingLayer::Policy,
&mode,
j.clone(),
test_command("policy-down"),
"",
None,
);
registry.register_at_layer(
BindingLayer::User,
&mode,
j.clone(),
test_command("user-down"),
"",
None,
);
assert_eq!(registry.binding_count(&mode), 1);
registry.clear_layer(BindingLayer::User, &mode);
let binding = registry.get_binding(&mode, &j);
assert_eq!(binding, Some(test_command("policy-down")));
}
#[test]
fn test_remove_at_layer() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
let keys = KeySequence::parse("x").unwrap();
registry.register_at_layer(
BindingLayer::Policy,
&mode,
keys.clone(),
test_command("delete-char"),
"",
None,
);
registry.remove_at_layer(BindingLayer::User, &mode, keys.clone());
let binding = registry.get_binding(&mode, &keys);
assert!(binding.is_none());
}
#[test]
fn test_bindings_for_mode() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
registry.register_str(&mode, "j", test_command("down"));
registry.register_str(&mode, "k", test_command("up"));
registry.register_str(&mode, "h", test_command("left"));
let bindings = registry.bindings_for_mode(&mode);
assert_eq!(bindings.len(), 3);
}
#[test]
fn test_bindings_for_mode_empty() {
let registry = KeymapRegistry::new();
let mode = test_mode();
let bindings = registry.bindings_for_mode(&mode);
assert!(bindings.is_empty());
}
#[test]
fn test_bindings_with_prefix() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
registry.register_str(&mode, "g", test_command("goto"));
registry.register_str(&mode, "gg", test_command("goto-top"));
registry.register_str(&mode, "gj", test_command("goto-next-visual"));
let g = KeySequence::parse("g").unwrap();
let bindings = registry.bindings_with_prefix(&mode, &g);
assert_eq!(bindings.len(), 2);
}
#[test]
fn test_bindings_with_prefix_filtered_removed() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
let gg = KeySequence::parse("gg").unwrap();
registry.register_at_layer(
BindingLayer::Policy,
&mode,
gg.clone(),
test_command("goto-top"),
"",
None,
);
registry.remove_at_layer(BindingLayer::User, &mode, gg);
let g = KeySequence::parse("g").unwrap();
let bindings = registry.bindings_with_prefix(&mode, &g);
assert!(bindings.is_empty());
}
#[test]
fn test_bindings_with_prefix_returns_metadata() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
registry.register_at_layer(
BindingLayer::Policy,
&mode,
KeySequence::parse("gg").unwrap(),
test_command("goto-top"),
"Go to first line",
Some("motion"),
);
let g = KeySequence::parse("g").unwrap();
let bindings = registry.bindings_with_prefix(&mode, &g);
assert_eq!(bindings.len(), 1);
let (_, info) = &bindings[0];
assert_eq!(info.command, test_command("goto-top"));
assert_eq!(info.description, "Go to first line");
assert_eq!(info.category, Some("motion"));
assert_eq!(info.layer, BindingLayer::Policy);
}
#[test]
fn test_bindings_with_prefix_user_layer_metadata() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
registry.register_at_layer(
BindingLayer::User,
&mode,
KeySequence::parse("gg").unwrap(),
test_command("custom-goto"),
"Custom goto",
Some("custom"),
);
let g = KeySequence::parse("g").unwrap();
let bindings = registry.bindings_with_prefix(&mode, &g);
assert_eq!(bindings.len(), 1);
let (_, info) = &bindings[0];
assert_eq!(info.layer, BindingLayer::User);
assert_eq!(info.description, "Custom goto");
assert_eq!(info.category, Some("custom"));
}
#[test]
fn test_total_bindings() {
let mut registry = KeymapRegistry::new();
let mode1 = ModeId::with_discriminant(TEST_MODULE, "NORMAL", 0);
let mode2 = ModeId::with_discriminant(TEST_MODULE, "INSERT", 1);
registry.register_str(&mode1, "j", test_command("down"));
registry.register_str(&mode1, "k", test_command("up"));
registry.register_str(&mode2, "a", test_command("append"));
assert_eq!(registry.total_bindings(), 3);
}
#[test]
fn test_modes_iterator() {
let mut registry = KeymapRegistry::new();
let mode1 = ModeId::with_discriminant(TEST_MODULE, "NORMAL", 0);
let mode2 = ModeId::with_discriminant(TEST_MODULE, "INSERT", 1);
registry.register_str(&mode1, "j", test_command("down"));
registry.register_str(&mode2, "a", test_command("append"));
assert_eq!(registry.modes().count(), 2);
}
#[test]
fn test_keymap_query_trait() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
let keys = KeySequence::parse("j").unwrap();
registry.register_str(&mode, "j", test_command("down"));
let query: &dyn KeymapQuery = ®istry;
let state = query.query(&mode, &keys);
assert!(matches!(state, KeyLookupState::ExactOnly(_)));
let exact = query.get_exact(&mode, &keys);
assert_eq!(exact, Some(test_command("down")));
let has_longer = query.has_longer_bindings(&mode, &keys);
assert!(!has_longer);
}
#[test]
fn test_keymap_query_trait_prefix() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
registry.register_str(&mode, "gg", test_command("goto-top"));
let g = KeySequence::parse("g").unwrap();
let query: &dyn KeymapQuery = ®istry;
let has_longer = query.has_longer_bindings(&mode, &g);
assert!(has_longer);
let bindings = query.bindings_with_prefix(&mode, &g);
assert_eq!(bindings.len(), 1);
}
#[test]
fn test_debug_format() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
registry.register_str(&mode, "j", test_command("down"));
let debug_str = format!("{registry:?}");
assert!(debug_str.contains("KeymapRegistry"));
assert!(debug_str.contains("total_bindings"));
}
#[test]
fn test_register_str_invalid() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
let result = registry.register_str(&mode, "<<invalid>>", test_command("noop"));
assert!(!result);
}
#[test]
fn test_query_not_found() {
let registry = KeymapRegistry::new();
let mode = test_mode();
let keys = KeySequence::parse("z").unwrap();
let state = registry.query(&mode, &keys);
assert_eq!(state, KeyLookupState::NotFound);
}
#[test]
fn test_query_exact_only() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
let keys = KeySequence::parse("j").unwrap();
registry.register_str(&mode, "j", test_command("down"));
let state = registry.query(&mode, &keys);
assert_eq!(state, KeyLookupState::ExactOnly(test_command("down")));
}
#[test]
fn test_query_prefix_only() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
registry.register_str(&mode, "gg", test_command("goto-top"));
let g = KeySequence::parse("g").unwrap();
let state = registry.query(&mode, &g);
assert_eq!(state, KeyLookupState::PrefixOnly);
}
#[test]
fn test_query_exact_with_longer() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
registry.register_str(&mode, "g", test_command("goto"));
registry.register_str(&mode, "gg", test_command("goto-top"));
let g = KeySequence::parse("g").unwrap();
let state = registry.query(&mode, &g);
assert_eq!(
state,
KeyLookupState::ExactWithLonger {
exact: test_command("goto")
}
);
}
#[test]
fn test_lookup_with_custom_policy() {
use reovim_driver_input::KeyLookupPolicy;
struct AlwaysExecutePolicy;
#[cfg_attr(coverage_nightly, coverage(off))]
impl KeyLookupPolicy for AlwaysExecutePolicy {
fn resolve(&self, state: KeyLookupState) -> KeyLookupResult {
match state {
KeyLookupState::ExactOnly(cmd) | KeyLookupState::ExactWithLonger { exact: cmd } => {
KeyLookupResult::Found(cmd)
}
_ => KeyLookupResult::NotFound,
}
}
}
let mut registry = KeymapRegistry::new();
let mode = test_mode();
registry.register_str(&mode, "g", test_command("goto"));
registry.register_str(&mode, "gg", test_command("goto-top"));
let g = KeySequence::parse("g").unwrap();
let policy = AlwaysExecutePolicy;
let result = registry.lookup_with_policy(&mode, &g, &policy);
assert!(result.is_found());
assert_eq!(result.command_id(), Some(&test_command("goto")));
}
#[test]
fn test_register_at_layer_for_module_replaces() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
let keys = KeySequence::parse("x").unwrap();
let owner = ModuleId::new("owner");
registry.register_at_layer_for_module(
&mode,
keys.clone(),
BindingInfo::from_command(test_command("delete1"), BindingLayer::Policy),
owner.clone(),
);
registry.register_at_layer_for_module(
&mode,
keys.clone(),
BindingInfo::from_command(test_command("delete2"), BindingLayer::Policy),
owner,
);
let binding = registry.get_binding(&mode, &keys);
assert_eq!(binding, Some(test_command("delete2")));
}
#[test]
fn test_clear_layer_removes_mode_if_empty() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
let keys = KeySequence::parse("j").unwrap();
registry.register_at_layer(BindingLayer::User, &mode, keys, test_command("down"), "", None);
assert!(!registry.is_empty());
registry.clear_layer(BindingLayer::User, &mode);
assert!(registry.is_empty());
}
#[test]
fn test_unregister_for_module_removes_mode_if_empty() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
let owner = ModuleId::new("owner");
let keys = KeySequence::parse("j").unwrap();
registry.register_at_layer_for_module(
&mode,
keys,
BindingInfo::from_command(test_command("down"), BindingLayer::Policy),
owner.clone(),
);
assert!(!registry.is_empty());
registry.unregister_for_module(&owner);
assert!(registry.is_empty());
}
#[test]
fn test_unregister_for_module_nonexistent() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
registry.register_str(&mode, "j", test_command("down"));
let nonexistent = ModuleId::new("nonexistent");
let removed = registry.unregister_for_module(&nonexistent);
assert_eq!(removed, 0);
assert_eq!(registry.binding_count(&mode), 1);
}
#[test]
fn test_has_longer_bindings_false() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
registry.register_str(&mode, "j", test_command("down"));
let j = KeySequence::parse("j").unwrap();
assert!(!registry.has_longer_bindings(&mode, &j));
}
#[test]
fn test_has_longer_bindings_true() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
registry.register_str(&mode, "gg", test_command("goto-top"));
let g = KeySequence::parse("g").unwrap();
assert!(registry.has_longer_bindings(&mode, &g));
}
#[test]
fn test_is_empty_with_empty_entries() {
let registry = KeymapRegistry::new();
assert!(registry.is_empty());
}
#[test]
fn test_binding_count_nonexistent_mode() {
let registry = KeymapRegistry::new();
let mode = test_mode();
assert_eq!(registry.binding_count(&mode), 0);
}
#[test]
fn test_layer_sorting() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
let keys = KeySequence::parse("x").unwrap();
registry.register_at_layer(
BindingLayer::Base,
&mode,
keys.clone(),
test_command("base"),
"",
None,
);
registry.register_at_layer(
BindingLayer::User,
&mode,
keys.clone(),
test_command("user"),
"",
None,
);
registry.register_at_layer(
BindingLayer::Policy,
&mode,
keys.clone(),
test_command("policy"),
"",
None,
);
let binding = registry.get_binding(&mode, &keys);
assert_eq!(binding, Some(test_command("user")));
}
#[test]
fn test_modes_empty_registry() {
let registry = KeymapRegistry::new();
assert_eq!(registry.modes().count(), 0);
}
#[test]
fn test_clear_layer_removes_all_bindings_and_cleans_empty_modes() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
let j = KeySequence::parse("j").unwrap();
registry.register_at_layer(BindingLayer::Policy, &mode, j, test_command("down"), "", None);
assert_eq!(registry.total_bindings(), 1);
registry.clear_layer(BindingLayer::Policy, &mode);
assert_eq!(registry.total_bindings(), 0);
assert_eq!(registry.modes().count(), 0);
}
#[test]
fn test_clear_layer_nonexistent_mode_is_noop() {
let mut registry = KeymapRegistry::new();
let mode = test_mode();
registry.clear_layer(BindingLayer::Policy, &mode);
assert_eq!(registry.total_bindings(), 0);
}
#[test]
fn test_set_default_policy() {
use reovim_driver_input::KeyLookupPolicy;
struct WaitForLongerPolicy;
#[cfg_attr(coverage_nightly, coverage(off))]
impl KeyLookupPolicy for WaitForLongerPolicy {
fn resolve(&self, state: KeyLookupState) -> KeyLookupResult {
match state {
KeyLookupState::ExactWithLonger { .. } | KeyLookupState::PrefixOnly => {
KeyLookupResult::Prefix
}
KeyLookupState::ExactOnly(cmd) => KeyLookupResult::Found(cmd),
KeyLookupState::NotFound => KeyLookupResult::NotFound,
}
}
}
let mut registry = KeymapRegistry::new();
let mode = test_mode();
registry.register_str(&mode, "g", test_command("goto"));
registry.register_str(&mode, "gg", test_command("goto-top"));
let g = KeySequence::parse("g").unwrap();
let result = registry.lookup(&mode, &g);
assert!(result.is_found());
registry.set_default_policy(Arc::new(WaitForLongerPolicy));
let result = registry.lookup(&mode, &g);
assert!(result.is_prefix());
}
#[test]
fn test_default_policy_is_eager() {
let registry = KeymapRegistry::new();
let default_registry = KeymapRegistry::default();
assert!(registry.is_empty());
assert!(default_registry.is_empty());
}