test(tui): add runtime keymap resolver characterization suite

Introduce the TUI runtime keymap resolver and keybinding matching helpers
with a dedicated unit-test suite. This commit is additive only: it adds
resolution logic, conflict validation, parser coverage, and documented macros
without wiring input handlers to the new runtime map yet.
This commit is contained in:
Josh McKinney
2026-04-23 10:34:37 -03:00
committed by Felipe Coury
parent 289ccad03c
commit ef07eb7efd
3 changed files with 1203 additions and 1 deletions
+74 -1
View File
@@ -15,7 +15,7 @@ const ALT_PREFIX: &str = "alt + ";
const CTRL_PREFIX: &str = "ctrl + ";
const SHIFT_PREFIX: &str = "shift + ";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) struct KeyBinding {
key: KeyCode,
modifiers: KeyModifiers,
@@ -31,6 +31,22 @@ impl KeyBinding {
&& self.modifiers == event.modifiers
&& (event.kind == KeyEventKind::Press || event.kind == KeyEventKind::Repeat)
}
pub(crate) const fn parts(&self) -> (KeyCode, KeyModifiers) {
(self.key, self.modifiers)
}
}
/// Matching helpers for one action's keybinding set.
pub(crate) trait KeyBindingListExt {
/// True when any binding in this set matches `event`.
fn is_pressed(&self, event: KeyEvent) -> bool;
}
impl KeyBindingListExt for [KeyBinding] {
fn is_pressed(&self, event: KeyEvent) -> bool {
self.iter().any(|binding| binding.is_press(event))
}
}
pub(crate) const fn plain(key: KeyCode) -> KeyBinding {
@@ -110,3 +126,60 @@ pub(crate) fn is_altgr(mods: KeyModifiers) -> bool {
pub(crate) fn is_altgr(_mods: KeyModifiers) -> bool {
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_press_accepts_press_and_repeat_but_rejects_release() {
let binding = ctrl(KeyCode::Char('k'));
let press = KeyEvent::new(KeyCode::Char('k'), KeyModifiers::CONTROL);
let repeat = KeyEvent {
kind: KeyEventKind::Repeat,
..press
};
let release = KeyEvent {
kind: KeyEventKind::Release,
..press
};
let wrong_modifiers = KeyEvent::new(KeyCode::Char('k'), KeyModifiers::NONE);
assert!(binding.is_press(press));
assert!(binding.is_press(repeat));
assert!(!binding.is_press(release));
assert!(!binding.is_press(wrong_modifiers));
}
#[test]
fn keybinding_list_ext_matches_any_binding() {
let bindings = [plain(KeyCode::Char('a')), ctrl(KeyCode::Char('b'))];
assert!(bindings.is_pressed(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE)));
assert!(bindings.is_pressed(KeyEvent::new(KeyCode::Char('b'), KeyModifiers::CONTROL)));
assert!(!bindings.is_pressed(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::NONE)));
}
#[test]
fn ctrl_alt_sets_both_modifiers() {
assert_eq!(
ctrl_alt(KeyCode::Char('v')).parts(),
(
KeyCode::Char('v'),
KeyModifiers::CONTROL | KeyModifiers::ALT
)
);
}
#[test]
fn has_ctrl_or_alt_checks_supported_modifier_combinations() {
assert!(!has_ctrl_or_alt(KeyModifiers::NONE));
assert!(has_ctrl_or_alt(KeyModifiers::CONTROL));
assert!(has_ctrl_or_alt(KeyModifiers::ALT));
#[cfg(windows)]
assert!(!has_ctrl_or_alt(KeyModifiers::CONTROL | KeyModifiers::ALT));
#[cfg(not(windows))]
assert!(has_ctrl_or_alt(KeyModifiers::CONTROL | KeyModifiers::ALT));
}
}
File diff suppressed because it is too large Load Diff
+1
View File
@@ -129,6 +129,7 @@ mod history_cell;
pub(crate) mod insert_history;
pub use insert_history::insert_history_lines;
mod key_hint;
mod keymap;
mod line_truncation;
pub(crate) mod live_wrap;
pub use live_wrap::RowBuilder;