fixes
This commit is contained in:
629
tests/vim_mode.rs
Normal file
629
tests/vim_mode.rs
Normal file
@@ -0,0 +1,629 @@
|
||||
//! Tests for Vim Normal/Insert mode feature
|
||||
//!
|
||||
//! Covers:
|
||||
//! - Mode transitions (i→Insert, Esc→Normal, auto-Insert on Reply/Edit)
|
||||
//! - Command blocking in Insert mode (vim keys type text)
|
||||
//! - Insert mode input handling (NewLine, DeleteWord, MoveToStart, MoveToEnd)
|
||||
//! - Close chat resets mode
|
||||
//! - Edge cases (Esc cancels Reply/Editing from Insert)
|
||||
|
||||
mod helpers;
|
||||
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
use helpers::app_builder::TestAppBuilder;
|
||||
use helpers::test_data::{create_test_chat, TestMessageBuilder};
|
||||
use tele_tui::app::InputMode;
|
||||
use tele_tui::app::methods::compose::ComposeMethods;
|
||||
use tele_tui::app::methods::messages::MessageMethods;
|
||||
use tele_tui::input::handle_main_input;
|
||||
|
||||
fn key(code: KeyCode) -> KeyEvent {
|
||||
KeyEvent::new(code, KeyModifiers::empty())
|
||||
}
|
||||
|
||||
fn ctrl_key(c: char) -> KeyEvent {
|
||||
KeyEvent::new(KeyCode::Char(c), KeyModifiers::CONTROL)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Mode Transitions
|
||||
// ============================================================
|
||||
|
||||
/// `i` в Normal mode → переход в Insert mode
|
||||
#[tokio::test]
|
||||
async fn test_i_enters_insert_mode() {
|
||||
let messages = vec![
|
||||
TestMessageBuilder::new("Hello", 1).outgoing().build(),
|
||||
];
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.with_messages(101, messages)
|
||||
.selecting_message(0)
|
||||
.build();
|
||||
|
||||
assert_eq!(app.input_mode, InputMode::Normal);
|
||||
|
||||
handle_main_input(&mut app, key(KeyCode::Char('i'))).await;
|
||||
|
||||
assert_eq!(app.input_mode, InputMode::Insert);
|
||||
// Выходим из MessageSelection
|
||||
assert!(!app.is_selecting_message());
|
||||
}
|
||||
|
||||
/// `ш` (русская i) в Normal mode → переход в Insert mode
|
||||
#[tokio::test]
|
||||
async fn test_russian_i_enters_insert_mode() {
|
||||
let messages = vec![
|
||||
TestMessageBuilder::new("Hello", 1).outgoing().build(),
|
||||
];
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.with_messages(101, messages)
|
||||
.selecting_message(0)
|
||||
.build();
|
||||
|
||||
handle_main_input(&mut app, key(KeyCode::Char('ш'))).await;
|
||||
|
||||
assert_eq!(app.input_mode, InputMode::Insert);
|
||||
}
|
||||
|
||||
/// Esc в Insert mode → Normal mode + MessageSelection
|
||||
#[tokio::test]
|
||||
async fn test_esc_exits_insert_mode() {
|
||||
let messages = vec![
|
||||
TestMessageBuilder::new("Hello", 1).outgoing().build(),
|
||||
];
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.with_messages(101, messages)
|
||||
.insert_mode()
|
||||
.build();
|
||||
|
||||
assert_eq!(app.input_mode, InputMode::Insert);
|
||||
|
||||
handle_main_input(&mut app, key(KeyCode::Esc)).await;
|
||||
|
||||
assert_eq!(app.input_mode, InputMode::Normal);
|
||||
assert!(app.is_selecting_message());
|
||||
}
|
||||
|
||||
/// Esc в Normal mode → закрывает чат
|
||||
#[tokio::test]
|
||||
async fn test_esc_in_normal_closes_chat() {
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.build();
|
||||
|
||||
assert!(app.selected_chat_id.is_some());
|
||||
|
||||
handle_main_input(&mut app, key(KeyCode::Esc)).await;
|
||||
|
||||
assert!(app.selected_chat_id.is_none());
|
||||
assert_eq!(app.input_mode, InputMode::Normal);
|
||||
}
|
||||
|
||||
/// close_chat() сбрасывает input_mode
|
||||
#[tokio::test]
|
||||
async fn test_close_chat_resets_input_mode() {
|
||||
use tele_tui::app::methods::navigation::NavigationMethods;
|
||||
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.insert_mode()
|
||||
.build();
|
||||
|
||||
assert_eq!(app.input_mode, InputMode::Insert);
|
||||
|
||||
app.close_chat();
|
||||
|
||||
assert_eq!(app.input_mode, InputMode::Normal);
|
||||
}
|
||||
|
||||
/// Auto-Insert при Reply (`r` в MessageSelection)
|
||||
#[tokio::test]
|
||||
async fn test_reply_auto_enters_insert_mode() {
|
||||
let messages = vec![
|
||||
TestMessageBuilder::new("Hello from friend", 1).sender("Friend").build(),
|
||||
];
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.with_messages(101, messages)
|
||||
.selecting_message(0)
|
||||
.build();
|
||||
|
||||
assert_eq!(app.input_mode, InputMode::Normal);
|
||||
|
||||
// `r` → reply
|
||||
handle_main_input(&mut app, key(KeyCode::Char('r'))).await;
|
||||
|
||||
assert_eq!(app.input_mode, InputMode::Insert);
|
||||
assert!(app.is_replying());
|
||||
}
|
||||
|
||||
/// Auto-Insert при Edit (Enter в MessageSelection)
|
||||
#[tokio::test]
|
||||
async fn test_edit_auto_enters_insert_mode() {
|
||||
let messages = vec![
|
||||
TestMessageBuilder::new("My message", 1).outgoing().build(),
|
||||
];
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.with_messages(101, messages)
|
||||
.selecting_message(0)
|
||||
.build();
|
||||
|
||||
assert_eq!(app.input_mode, InputMode::Normal);
|
||||
|
||||
// Enter → edit selected message
|
||||
handle_main_input(&mut app, key(KeyCode::Enter)).await;
|
||||
|
||||
assert_eq!(app.input_mode, InputMode::Insert);
|
||||
assert!(app.is_editing());
|
||||
}
|
||||
|
||||
/// При открытии чата → Normal mode (selected_chat задан builder'ом, как после open)
|
||||
#[test]
|
||||
fn test_open_chat_defaults_to_normal_mode() {
|
||||
// Проверяем что при настройке чата (аналог состояния после open_chat_and_load_data)
|
||||
// режим = Normal, и start_message_selection() корректно входит в MessageSelection
|
||||
let messages = vec![
|
||||
TestMessageBuilder::new("Msg 1", 1).build(),
|
||||
TestMessageBuilder::new("Msg 2", 2).build(),
|
||||
];
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.with_messages(101, messages)
|
||||
.build();
|
||||
|
||||
assert_eq!(app.input_mode, InputMode::Normal);
|
||||
assert!(app.selected_chat_id.is_some());
|
||||
|
||||
// open_chat_and_load_data вызывает start_message_selection()
|
||||
app.start_message_selection();
|
||||
assert!(app.is_selecting_message());
|
||||
}
|
||||
|
||||
/// После отправки сообщения — остаёмся в Insert
|
||||
#[tokio::test]
|
||||
async fn test_send_message_stays_in_insert() {
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.insert_mode()
|
||||
.message_input("Hello!")
|
||||
.build();
|
||||
app.cursor_position = 6;
|
||||
|
||||
assert_eq!(app.input_mode, InputMode::Insert);
|
||||
|
||||
// Enter → отправить
|
||||
handle_main_input(&mut app, key(KeyCode::Enter)).await;
|
||||
|
||||
// Остаёмся в Insert
|
||||
assert_eq!(app.input_mode, InputMode::Insert);
|
||||
// Инпут очищен
|
||||
assert_eq!(app.message_input, "");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Command Blocking in Insert Mode
|
||||
// ============================================================
|
||||
|
||||
/// `j` в Insert mode → набирает символ, НЕ навигация
|
||||
#[tokio::test]
|
||||
async fn test_j_types_in_insert_mode() {
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.insert_mode()
|
||||
.build();
|
||||
|
||||
handle_main_input(&mut app, key(KeyCode::Char('j'))).await;
|
||||
|
||||
assert_eq!(app.message_input, "j");
|
||||
}
|
||||
|
||||
/// `k` в Insert mode → набирает символ
|
||||
#[tokio::test]
|
||||
async fn test_k_types_in_insert_mode() {
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.insert_mode()
|
||||
.build();
|
||||
|
||||
handle_main_input(&mut app, key(KeyCode::Char('k'))).await;
|
||||
|
||||
assert_eq!(app.message_input, "k");
|
||||
}
|
||||
|
||||
/// `d` в Insert mode → набирает "d", НЕ удаляет сообщение
|
||||
#[tokio::test]
|
||||
async fn test_d_types_in_insert_mode() {
|
||||
let messages = vec![
|
||||
TestMessageBuilder::new("Hello", 1).outgoing().build(),
|
||||
];
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.with_messages(101, messages)
|
||||
.insert_mode()
|
||||
.build();
|
||||
|
||||
handle_main_input(&mut app, key(KeyCode::Char('d'))).await;
|
||||
|
||||
assert_eq!(app.message_input, "d");
|
||||
// НЕ вошли в delete confirmation
|
||||
assert!(!app.chat_state.is_delete_confirmation());
|
||||
}
|
||||
|
||||
/// `r` в Insert mode → набирает "r", НЕ reply
|
||||
#[tokio::test]
|
||||
async fn test_r_types_in_insert_mode() {
|
||||
let messages = vec![
|
||||
TestMessageBuilder::new("Hello", 1).build(),
|
||||
];
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.with_messages(101, messages)
|
||||
.insert_mode()
|
||||
.build();
|
||||
|
||||
handle_main_input(&mut app, key(KeyCode::Char('r'))).await;
|
||||
|
||||
assert_eq!(app.message_input, "r");
|
||||
assert!(!app.is_replying());
|
||||
}
|
||||
|
||||
/// `f` в Insert mode → набирает "f", НЕ forward
|
||||
#[tokio::test]
|
||||
async fn test_f_types_in_insert_mode() {
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.insert_mode()
|
||||
.build();
|
||||
|
||||
handle_main_input(&mut app, key(KeyCode::Char('f'))).await;
|
||||
|
||||
assert_eq!(app.message_input, "f");
|
||||
assert!(!app.is_forwarding());
|
||||
}
|
||||
|
||||
/// `q` в Insert mode → набирает "q", НЕ quit
|
||||
#[tokio::test]
|
||||
async fn test_q_types_in_insert_mode() {
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.insert_mode()
|
||||
.build();
|
||||
|
||||
handle_main_input(&mut app, key(KeyCode::Char('q'))).await;
|
||||
|
||||
assert_eq!(app.message_input, "q");
|
||||
}
|
||||
|
||||
/// Ctrl+S в Insert mode → НЕ открывает поиск
|
||||
#[tokio::test]
|
||||
async fn test_ctrl_s_blocked_in_insert_mode() {
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.insert_mode()
|
||||
.build();
|
||||
|
||||
handle_main_input(&mut app, ctrl_key('s')).await;
|
||||
|
||||
assert!(!app.is_searching);
|
||||
}
|
||||
|
||||
/// Ctrl+F в Insert mode → НЕ открывает поиск по сообщениям
|
||||
#[tokio::test]
|
||||
async fn test_ctrl_f_blocked_in_insert_mode() {
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.insert_mode()
|
||||
.build();
|
||||
|
||||
handle_main_input(&mut app, ctrl_key('f')).await;
|
||||
|
||||
assert!(!app.chat_state.is_search_in_chat());
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Normal Mode — commands work
|
||||
// ============================================================
|
||||
|
||||
/// `j` в Normal mode → навигация вниз (MoveDown) в MessageSelection
|
||||
#[tokio::test]
|
||||
async fn test_j_navigates_in_normal_mode() {
|
||||
let messages = vec![
|
||||
TestMessageBuilder::new("Msg 1", 1).build(),
|
||||
TestMessageBuilder::new("Msg 2", 2).build(),
|
||||
TestMessageBuilder::new("Msg 3", 3).build(),
|
||||
];
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.with_messages(101, messages)
|
||||
.selecting_message(1)
|
||||
.build();
|
||||
|
||||
assert_eq!(app.input_mode, InputMode::Normal);
|
||||
assert_eq!(app.chat_state.selected_message_index(), Some(1));
|
||||
|
||||
handle_main_input(&mut app, key(KeyCode::Char('j'))).await;
|
||||
|
||||
// j = MoveDown = select_next_message
|
||||
assert_eq!(app.chat_state.selected_message_index(), Some(2));
|
||||
// Текст НЕ добавился
|
||||
assert_eq!(app.message_input, "");
|
||||
}
|
||||
|
||||
/// `k` в Normal mode → навигация вверх в MessageSelection
|
||||
#[tokio::test]
|
||||
async fn test_k_navigates_in_normal_mode() {
|
||||
let messages = vec![
|
||||
TestMessageBuilder::new("Msg 1", 1).build(),
|
||||
TestMessageBuilder::new("Msg 2", 2).build(),
|
||||
TestMessageBuilder::new("Msg 3", 3).build(),
|
||||
];
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.with_messages(101, messages)
|
||||
.selecting_message(2)
|
||||
.build();
|
||||
|
||||
handle_main_input(&mut app, key(KeyCode::Char('k'))).await;
|
||||
|
||||
assert_eq!(app.chat_state.selected_message_index(), Some(1));
|
||||
assert_eq!(app.message_input, "");
|
||||
}
|
||||
|
||||
/// `d` в Normal mode → показывает подтверждение удаления
|
||||
#[tokio::test]
|
||||
async fn test_d_deletes_in_normal_mode() {
|
||||
let messages = vec![
|
||||
TestMessageBuilder::new("My message", 1).outgoing().build(),
|
||||
];
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.with_messages(101, messages)
|
||||
.selecting_message(0)
|
||||
.build();
|
||||
|
||||
handle_main_input(&mut app, key(KeyCode::Char('d'))).await;
|
||||
|
||||
assert!(app.chat_state.is_delete_confirmation());
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Insert Mode Input Handling
|
||||
// ============================================================
|
||||
|
||||
/// Ctrl+W → удаляет слово в Insert mode
|
||||
#[tokio::test]
|
||||
async fn test_ctrl_w_deletes_word_in_insert() {
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.insert_mode()
|
||||
.message_input("Hello World")
|
||||
.build();
|
||||
app.cursor_position = 11; // конец "Hello World"
|
||||
|
||||
handle_main_input(&mut app, ctrl_key('w')).await;
|
||||
|
||||
assert_eq!(app.message_input, "Hello ");
|
||||
assert_eq!(app.cursor_position, 6);
|
||||
}
|
||||
|
||||
/// Ctrl+W → удаляет слово + пробелы перед ним
|
||||
#[tokio::test]
|
||||
async fn test_ctrl_w_deletes_word_with_spaces() {
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.insert_mode()
|
||||
.message_input("one two three")
|
||||
.build();
|
||||
app.cursor_position = 14; // конец
|
||||
|
||||
handle_main_input(&mut app, ctrl_key('w')).await;
|
||||
|
||||
// "one two " → удалили "three", осталось "one two "
|
||||
assert_eq!(app.message_input, "one two ");
|
||||
assert_eq!(app.cursor_position, 9);
|
||||
}
|
||||
|
||||
/// Ctrl+A → курсор в начало в Insert mode
|
||||
#[tokio::test]
|
||||
async fn test_ctrl_a_moves_to_start_in_insert() {
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.insert_mode()
|
||||
.message_input("Hello World")
|
||||
.build();
|
||||
app.cursor_position = 11;
|
||||
|
||||
handle_main_input(&mut app, ctrl_key('a')).await;
|
||||
|
||||
assert_eq!(app.cursor_position, 0);
|
||||
}
|
||||
|
||||
/// Ctrl+E → курсор в конец в Insert mode
|
||||
#[tokio::test]
|
||||
async fn test_ctrl_e_moves_to_end_in_insert() {
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.insert_mode()
|
||||
.message_input("Hello World")
|
||||
.build();
|
||||
app.cursor_position = 0;
|
||||
|
||||
handle_main_input(&mut app, ctrl_key('e')).await;
|
||||
|
||||
assert_eq!(app.cursor_position, 11);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Edge Cases — Esc from Insert cancels Reply/Editing
|
||||
// ============================================================
|
||||
|
||||
/// Esc из Insert при активном Reply → отменяет reply + Normal + MessageSelection
|
||||
#[tokio::test]
|
||||
async fn test_esc_from_insert_cancels_reply() {
|
||||
let messages = vec![
|
||||
TestMessageBuilder::new("Hello", 1).sender("Friend").build(),
|
||||
];
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.with_messages(101, messages)
|
||||
.insert_mode()
|
||||
.replying_to(1)
|
||||
.build();
|
||||
|
||||
assert!(app.is_replying());
|
||||
assert_eq!(app.input_mode, InputMode::Insert);
|
||||
|
||||
handle_main_input(&mut app, key(KeyCode::Esc)).await;
|
||||
|
||||
assert!(!app.is_replying());
|
||||
assert_eq!(app.input_mode, InputMode::Normal);
|
||||
assert!(app.is_selecting_message());
|
||||
}
|
||||
|
||||
/// Esc из Insert при активном Editing → отменяет editing + Normal + MessageSelection
|
||||
#[tokio::test]
|
||||
async fn test_esc_from_insert_cancels_editing() {
|
||||
let messages = vec![
|
||||
TestMessageBuilder::new("My message", 1).outgoing().build(),
|
||||
];
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.with_messages(101, messages)
|
||||
.insert_mode()
|
||||
.editing_message(1, 0)
|
||||
.message_input("Edited text")
|
||||
.build();
|
||||
|
||||
assert!(app.is_editing());
|
||||
assert_eq!(app.input_mode, InputMode::Insert);
|
||||
|
||||
handle_main_input(&mut app, key(KeyCode::Esc)).await;
|
||||
|
||||
assert!(!app.is_editing());
|
||||
assert_eq!(app.input_mode, InputMode::Normal);
|
||||
assert!(app.is_selecting_message());
|
||||
// Инпут очищен (cancel_editing)
|
||||
assert_eq!(app.message_input, "");
|
||||
}
|
||||
|
||||
/// Normal mode auto-enters MessageSelection при первом нажатии
|
||||
/// Используем `k` (MoveUp), т.к. `j` (MoveDown) на последнем сообщении выходит из selection
|
||||
#[tokio::test]
|
||||
async fn test_normal_mode_auto_enters_selection_on_any_key() {
|
||||
let messages = vec![
|
||||
TestMessageBuilder::new("Msg 1", 1).build(),
|
||||
TestMessageBuilder::new("Msg 2", 2).build(),
|
||||
];
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.with_messages(101, messages)
|
||||
.build();
|
||||
|
||||
// ChatState::Normal, InputMode::Normal — не в MessageSelection
|
||||
assert!(!app.is_selecting_message());
|
||||
|
||||
// `k` (MoveUp) в Normal mode → auto-enter MessageSelection + move up
|
||||
handle_main_input(&mut app, key(KeyCode::Char('k'))).await;
|
||||
|
||||
assert!(app.is_selecting_message());
|
||||
// Начали с последнего (index 1), MoveUp → index 0
|
||||
assert_eq!(app.chat_state.selected_message_index(), Some(0));
|
||||
}
|
||||
|
||||
/// Полный цикл: Normal → i → набор текста → Esc → Normal
|
||||
#[tokio::test]
|
||||
async fn test_full_mode_cycle() {
|
||||
let messages = vec![
|
||||
TestMessageBuilder::new("Msg", 1).build(),
|
||||
];
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.with_messages(101, messages)
|
||||
.selecting_message(0)
|
||||
.build();
|
||||
|
||||
// 1. Normal mode
|
||||
assert_eq!(app.input_mode, InputMode::Normal);
|
||||
assert!(app.is_selecting_message());
|
||||
|
||||
// 2. i → Insert
|
||||
handle_main_input(&mut app, key(KeyCode::Char('i'))).await;
|
||||
assert_eq!(app.input_mode, InputMode::Insert);
|
||||
assert!(!app.is_selecting_message());
|
||||
|
||||
// 3. Набираем текст
|
||||
handle_main_input(&mut app, key(KeyCode::Char('H'))).await;
|
||||
handle_main_input(&mut app, key(KeyCode::Char('i'))).await;
|
||||
assert_eq!(app.message_input, "Hi");
|
||||
|
||||
// 4. Esc → Normal + MessageSelection
|
||||
handle_main_input(&mut app, key(KeyCode::Esc)).await;
|
||||
assert_eq!(app.input_mode, InputMode::Normal);
|
||||
assert!(app.is_selecting_message());
|
||||
// Текст сохранён (черновик)
|
||||
assert_eq!(app.message_input, "Hi");
|
||||
}
|
||||
|
||||
/// Полный цикл: Normal → r (reply) → набор → Enter (отправка) → остаёмся в Insert
|
||||
#[tokio::test]
|
||||
async fn test_reply_send_stays_insert() {
|
||||
let messages = vec![
|
||||
TestMessageBuilder::new("Question?", 1).sender("Friend").build(),
|
||||
];
|
||||
let mut app = TestAppBuilder::new()
|
||||
.with_chats(vec![create_test_chat("Chat", 101)])
|
||||
.selected_chat(101)
|
||||
.with_messages(101, messages)
|
||||
.selecting_message(0)
|
||||
.build();
|
||||
|
||||
// 1. r → auto-Insert + Reply
|
||||
handle_main_input(&mut app, key(KeyCode::Char('r'))).await;
|
||||
assert_eq!(app.input_mode, InputMode::Insert);
|
||||
assert!(app.is_replying());
|
||||
|
||||
// 2. Набираем ответ
|
||||
for c in "Yes!".chars() {
|
||||
handle_main_input(&mut app, key(KeyCode::Char(c))).await;
|
||||
}
|
||||
assert_eq!(app.message_input, "Yes!");
|
||||
|
||||
// 3. Enter → отправить
|
||||
handle_main_input(&mut app, key(KeyCode::Enter)).await;
|
||||
|
||||
// Остаёмся в Insert после отправки
|
||||
assert_eq!(app.input_mode, InputMode::Insert);
|
||||
assert_eq!(app.message_input, "");
|
||||
}
|
||||
Reference in New Issue
Block a user