use crate::common::harness::EditorTestHarness;
use crossterm::event::{KeyCode, KeyModifiers};
use portable_pty::{native_pty_system, PtySize};
fn open_keybinding_editor(harness: &mut EditorTestHarness) {
harness.editor_mut().open_keybinding_editor();
harness.render().unwrap();
}
#[test]
fn test_open_keybinding_editor() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
harness.render().unwrap();
harness.assert_screen_not_contains("Keybinding Editor");
open_keybinding_editor(&mut harness);
harness.assert_screen_contains("Keybinding Editor");
harness.assert_screen_contains("bindings");
harness.assert_screen_contains("Config:");
}
#[test]
fn test_close_keybinding_editor_with_escape() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness.assert_screen_contains("Keybinding Editor");
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness.assert_screen_not_contains("Keybinding Editor");
}
#[test]
fn test_navigate_bindings_with_arrows() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
let screen_before = harness.screen_to_string();
for _ in 0..5 {
harness.send_key(KeyCode::Down, KeyModifiers::NONE).unwrap();
}
harness.render().unwrap();
let screen_after = harness.screen_to_string();
assert_ne!(
screen_before, screen_after,
"Selection should have moved after pressing Down"
);
for _ in 0..3 {
harness.send_key(KeyCode::Up, KeyModifiers::NONE).unwrap();
}
harness.render().unwrap();
harness.assert_screen_contains("Keybinding Editor");
}
#[test]
fn test_home_end_navigation() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness.send_key(KeyCode::End, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let screen_end = harness.screen_to_string();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let screen_home = harness.screen_to_string();
assert_ne!(
screen_end, screen_home,
"Home and End should show different parts of the list"
);
}
#[test]
fn test_page_up_down_navigation() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
let screen_initial = harness.screen_to_string();
harness
.send_key(KeyCode::PageDown, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen_page_down = harness.screen_to_string();
assert_ne!(
screen_initial, screen_page_down,
"PageDown should scroll the list"
);
harness
.send_key(KeyCode::PageUp, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
}
#[test]
fn test_text_search() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('/'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
for ch in "save".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness.render().unwrap();
harness.assert_screen_contains("save");
let screen = harness.screen_to_string();
assert!(
screen.contains("save") || screen.contains("Save"),
"Search for 'save' should show matching bindings"
);
}
#[test]
fn test_search_persists_after_enter() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('/'), KeyModifiers::NONE)
.unwrap();
for ch in "undo".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness.render().unwrap();
harness.assert_screen_contains("undo");
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("undo");
}
#[test]
fn test_escape_cancels_search() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('/'), KeyModifiers::NONE)
.unwrap();
for ch in "save".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness.render().unwrap();
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Keybinding Editor");
harness.assert_screen_contains("bindings");
}
#[test]
fn test_search_down_arrow_moves_to_list() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('/'), KeyModifiers::NONE)
.unwrap();
for ch in "copy".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness.render().unwrap();
harness.send_key(KeyCode::Down, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("copy");
harness.send_key(KeyCode::Down, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Keybinding Editor");
}
#[test]
fn test_context_filter_cycle() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness.assert_screen_contains("[All]");
harness
.send_key(KeyCode::Char('c'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("Context:"),
"Should still show the Context label"
);
}
#[test]
fn test_source_filter_cycle() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness.assert_screen_contains("[All]");
harness
.send_key(KeyCode::Char('s'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("Source:"),
"Should still show the Source label"
);
}
#[test]
fn test_help_overlay() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('?'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Keyboard Shortcuts");
harness.assert_screen_contains("Navigation");
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness.assert_screen_not_contains("Keyboard Shortcuts");
harness.assert_screen_contains("Keybinding Editor");
}
#[test]
fn test_open_edit_dialog() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Edit Keybinding");
harness.assert_screen_contains("Key:");
harness.assert_screen_contains("Action:");
harness.assert_screen_contains("Context:");
harness.assert_screen_contains("Save");
harness.assert_screen_contains("Cancel");
}
#[test]
fn test_close_edit_dialog_with_escape() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Edit Keybinding");
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness.assert_screen_not_contains("Edit Keybinding");
harness.assert_screen_contains("Keybinding Editor");
}
#[test]
fn test_edit_dialog_tab_focus() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Edit Keybinding");
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Edit Keybinding");
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Edit Keybinding");
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
}
#[test]
fn test_edit_dialog_tab_cycles_through_cancel() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Edit Keybinding");
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Edit Keybinding");
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Edit Keybinding");
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_not_contains("Edit Keybinding");
harness.assert_screen_contains("Keybinding Editor");
}
#[test]
fn test_open_add_dialog() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('a'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Add Keybinding");
harness.assert_screen_contains("Key:");
harness.assert_screen_contains("Action:");
harness.assert_screen_contains("Context:");
}
#[test]
fn test_add_new_binding() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('a'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Add Keybinding");
harness
.send_key(KeyCode::Char('k'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Ctrl+K");
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
for ch in "save".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("modified");
}
#[test]
fn test_delete_keymap_binding_creates_noop_override() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('/'), KeyModifiers::NONE)
.unwrap();
for ch in "save".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let mut found_keymap = false;
for _ in 0..20 {
let screen = harness.screen_to_string();
for line in screen.lines() {
if line.contains(">") && line.contains("keymap") && line.contains("Ctrl+S") {
found_keymap = true;
break;
}
}
if found_keymap {
break;
}
harness.send_key(KeyCode::Down, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
}
assert!(
found_keymap,
"Should find the Ctrl+S save keymap binding in search results"
);
harness
.send_key(KeyCode::Char('d'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("disabled") || screen.contains("override") || screen.contains("noop"),
"Should show a status message about the keymap binding being disabled.\nScreen:\n{}",
screen,
);
assert!(
screen.contains("modified"),
"Editor should show [modified] after overriding a keymap binding"
);
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Char('r'), KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("noop"),
"After overriding keymap binding, Ctrl+S should show 'noop' action.\nScreen:\n{}",
screen,
);
assert!(
screen.contains("custom"),
"The noop override should have 'custom' source.\nScreen:\n{}",
screen,
);
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Char('/'), KeyModifiers::NONE)
.unwrap();
for ch in "save".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("save"),
"The 'save' action should still appear in search results.\nScreen:\n{}",
screen,
);
let has_unbound_save = screen
.lines()
.any(|line| line.contains("save") && !line.contains("Ctrl+S") && !line.contains("keymap"));
assert!(
has_unbound_save,
"The 'save' action should appear without Ctrl+S key (unbound).\nScreen:\n{}",
screen,
);
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.assert_screen_not_contains("Keybinding Editor");
harness.type_text("x").unwrap();
harness.render().unwrap();
let buffer_before = harness.get_buffer_content().unwrap();
harness
.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
let buffer_after = harness.get_buffer_content().unwrap();
assert_eq!(
buffer_after, buffer_before,
"Buffer content should be unchanged — Ctrl+S should be a noop now"
);
}
#[test]
fn test_cannot_delete_unbound_action() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('d'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("Cannot") || screen.contains("cannot") || screen.contains("delete"),
"Should show a message about not being able to delete unbound actions.\nScreen:\n{}",
screen,
);
}
#[test]
fn test_unsaved_changes_confirm_dialog() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('a'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Char('k'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
for ch in "save".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Unsaved Changes");
harness.assert_screen_contains("Save");
harness.assert_screen_contains("Discard");
harness.assert_screen_contains("Cancel");
}
#[test]
fn test_confirm_dialog_cancel() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('a'), KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Char('k'), KeyModifiers::CONTROL)
.unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
for ch in "save".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Unsaved Changes");
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Keybinding Editor");
harness.assert_screen_not_contains("Unsaved Changes");
}
#[test]
fn test_confirm_dialog_discard() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('a'), KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Char('k'), KeyModifiers::CONTROL)
.unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
for ch in "save".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Unsaved Changes");
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_not_contains("Keybinding Editor");
}
#[test]
fn test_mouse_scroll() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
let screen_before = harness.screen_to_string();
harness.mouse_scroll_down(60, 20).unwrap();
harness.mouse_scroll_down(60, 20).unwrap();
harness.mouse_scroll_down(60, 20).unwrap();
let screen_after = harness.screen_to_string();
assert_ne!(
screen_before, screen_after,
"Mouse scroll should move the selection"
);
}
#[test]
fn test_mouse_click_selects_row() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
let screen_before = harness.screen_to_string();
harness.mouse_click(60, 15).unwrap();
harness.render().unwrap();
let screen_after = harness.screen_to_string();
assert_ne!(
screen_before, screen_after,
"Mouse click should select a different row"
);
}
#[test]
fn test_mouse_events_masked() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
harness.type_text("Hello world").unwrap();
harness.render().unwrap();
open_keybinding_editor(&mut harness);
harness.mouse_click(5, 2).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Keybinding Editor");
harness.mouse_scroll_down(5, 2).unwrap();
harness.assert_screen_contains("Keybinding Editor");
}
#[test]
fn test_record_key_search() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('r'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Record Key:");
harness
.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Ctrl+S");
}
#[test]
fn test_save_changes_with_ctrl_s() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('a'), KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Char('k'), KeyModifiers::CONTROL)
.unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
for ch in "save".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("modified");
harness
.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.assert_screen_not_contains("Keybinding Editor");
}
#[test]
fn test_action_field_autocomplete() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('a'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Char('k'), KeyModifiers::CONTROL)
.unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
for ch in "und".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("undo"),
"Autocomplete should show 'undo' suggestion for 'und'"
);
}
#[test]
fn test_edit_dialog_context_cycling() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('a'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("normal");
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("prompt") || screen.contains("popup") || screen.contains("global"),
"Context should have cycled to a different value"
);
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
}
#[test]
fn test_table_shows_columns() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness.assert_screen_contains("Key");
harness.assert_screen_contains("Action");
harness.assert_screen_contains("Description");
harness.assert_screen_contains("Context");
harness.assert_screen_contains("Source");
}
#[test]
fn test_bindings_count_displayed() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness.assert_screen_contains("bindings");
}
#[test]
fn test_footer_hints_displayed() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness.assert_screen_contains("Edit");
harness.assert_screen_contains("Add");
harness.assert_screen_contains("Delete");
harness.assert_screen_contains("Search");
harness.assert_screen_contains("Help");
harness.assert_screen_contains("Close");
}
#[test]
fn test_render_narrow_terminal_unicode_keys() {
let mut harness = EditorTestHarness::new(80, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('/'), KeyModifiers::NONE)
.unwrap();
for ch in "block_select".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness.render().unwrap();
harness.assert_screen_contains("Keybinding Editor");
}
#[test]
fn test_selected_item_stays_visible_when_scrolling() {
let mut harness = EditorTestHarness::new(120, 24).unwrap();
open_keybinding_editor(&mut harness);
harness.assert_screen_contains(">");
for i in 0..40 {
harness.send_key(KeyCode::Down, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
assert!(
harness.screen_to_string().contains(">"),
"Selection indicator '>' not visible after pressing Down {} times",
i + 1,
);
}
for i in 0..40 {
harness.send_key(KeyCode::Up, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
assert!(
harness.screen_to_string().contains(">"),
"Selection indicator '>' not visible after pressing Up {} times",
i + 1,
);
}
}
#[test]
fn test_unbound_actions_are_listed() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('/'), KeyModifiers::NONE)
.unwrap();
for ch in "duplicate_line".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("duplicate_line") || screen.contains("Duplicate"),
"Unbound action 'duplicate_line' should be listed in the keybinding editor"
);
}
#[test]
fn test_edit_unbound_action() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('/'), KeyModifiers::NONE)
.unwrap();
for ch in "duplicate_line".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Edit Keybinding");
harness.assert_screen_contains("Key:");
harness.assert_screen_contains("Action:");
harness.assert_screen_contains("duplicate_line");
harness
.send_key(
KeyCode::Char('d'),
KeyModifiers::CONTROL | KeyModifiers::SHIFT,
)
.unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_not_contains("Edit Keybinding");
harness.assert_screen_contains("modified");
}
#[test]
fn test_deleted_binding_appears_as_unbound() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('a'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Add Keybinding");
harness
.send_key(
KeyCode::Char('d'),
KeyModifiers::CONTROL | KeyModifiers::SHIFT,
)
.unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
for ch in "duplicate_line".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("modified");
harness
.send_key(KeyCode::Char('/'), KeyModifiers::NONE)
.unwrap();
for ch in "duplicate_line".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("custom"),
"Custom binding for duplicate_line should show 'custom' source"
);
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let mut found_custom = false;
for _ in 0..5 {
let current_screen = harness.screen_to_string();
for line in current_screen.lines() {
if line.contains(">") && line.contains("custom") {
found_custom = true;
break;
}
}
if found_custom {
break;
}
harness.send_key(KeyCode::Down, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
}
harness
.send_key(KeyCode::Char('d'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Char('/'), KeyModifiers::NONE)
.unwrap();
for ch in "duplicate_line".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("duplicate_line") || screen.contains("Duplicate"),
"After deleting the custom binding, duplicate_line should still appear as unbound"
);
assert!(
!screen.contains("custom"),
"After deleting the custom binding, there should be no 'custom' source for duplicate_line"
);
}
#[test]
fn test_delete_binding_full_flow() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('a'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Add Keybinding");
harness
.send_key(
KeyCode::Char('d'),
KeyModifiers::CONTROL | KeyModifiers::SHIFT,
)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Ctrl+Shift+D");
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
for ch in "duplicate_line".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("modified");
harness
.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.assert_screen_not_contains("Keybinding Editor");
harness.type_text("aaa").unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness
.send_key(
KeyCode::Char('d'),
KeyModifiers::CONTROL | KeyModifiers::SHIFT,
)
.unwrap();
harness.render().unwrap();
let buffer_content = harness.get_buffer_content().unwrap();
assert_eq!(
buffer_content, "aaa\naaa",
"Before delete: Ctrl+Shift+D should duplicate the line (binding is active)"
);
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('/'), KeyModifiers::NONE)
.unwrap();
for ch in "duplicate_line".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("Ctrl+Shift+D"),
"Before delete: table should show Ctrl+Shift+D for the binding"
);
assert!(
screen.contains("custom"),
"Before delete: table should show 'custom' source"
);
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let mut found_custom = false;
for _ in 0..10 {
let current_screen = harness.screen_to_string();
for line in current_screen.lines() {
if line.contains(">") && line.contains("custom") {
found_custom = true;
break;
}
}
if found_custom {
break;
}
harness.send_key(KeyCode::Down, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
}
assert!(found_custom, "Should find the custom binding row to delete");
harness
.send_key(KeyCode::Char('d'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("duplicate_line") || screen.contains("Duplicate"),
"After delete (before save): action should still be listed"
);
assert!(
!screen.contains("Ctrl+Shift+D"),
"After delete (before save): Ctrl+Shift+D should be gone from the table immediately"
);
assert!(
!screen.contains("custom"),
"After delete (before save): 'custom' source should be gone immediately"
);
harness
.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.assert_screen_not_contains("Keybinding Editor");
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('/'), KeyModifiers::NONE)
.unwrap();
for ch in "duplicate_line".chars() {
harness
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
.unwrap();
}
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("duplicate_line") || screen.contains("Duplicate"),
"After delete: action should still be listed in the table"
);
assert!(
!screen.contains("Ctrl+Shift+D"),
"After delete: Ctrl+Shift+D should NOT appear in the table (binding was deleted)"
);
assert!(
!screen.contains("custom"),
"After delete: 'custom' source should NOT appear"
);
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let buffer_before = harness.get_buffer_content().unwrap();
harness
.send_key(
KeyCode::Char('d'),
KeyModifiers::CONTROL | KeyModifiers::SHIFT,
)
.unwrap();
harness.render().unwrap();
let buffer_after = harness.get_buffer_content().unwrap();
assert_eq!(
buffer_after, buffer_before,
"After delete: Ctrl+Shift+D should have no effect (binding was removed)"
);
}
#[test]
fn test_add_binding_conflict_warning_for_existing_key() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
open_keybinding_editor(&mut harness);
harness
.send_key(KeyCode::Char('a'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Add Keybinding");
harness
.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Ctrl+S");
let screen = harness.screen_to_string();
assert!(
screen.contains("Conflict"),
"Should show conflict warning when using a key already bound to another action.\nScreen:\n{}",
screen
);
}
#[test]
fn test_keybinding_editor_captures_keys_over_terminal_mode() {
if native_pty_system()
.openpty(PtySize {
rows: 1,
cols: 1,
pixel_width: 0,
pixel_height: 0,
})
.is_err()
{
eprintln!("Skipping test: PTY not available");
return;
}
let mut harness = EditorTestHarness::new(120, 40).unwrap();
harness.editor_mut().open_terminal();
harness.render().unwrap();
assert!(
harness.editor().is_terminal_mode(),
"Should be in terminal mode after opening terminal"
);
open_keybinding_editor(&mut harness);
harness.assert_screen_contains("Keybinding Editor");
harness.assert_screen_contains(">");
for _ in 0..3 {
harness.send_key(KeyCode::Down, KeyModifiers::NONE).unwrap();
}
harness.render().unwrap();
harness.assert_screen_contains("Keybinding Editor");
harness.assert_screen_contains(">");
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness.assert_screen_not_contains("Keybinding Editor");
}