Files
telegram-tui/tests/input_navigation.rs
Mikhail Kilin ffd52d2384 refactor: complete Phase 13 deep architecture refactoring (etaps 3-7)
Split monolithic files into modular architecture:
- ui/messages.rs (893→365 lines): extract modals/, compose_bar.rs
- tdlib/messages.rs (836→3 files): split into messages/mod, convert, operations
- config/mod.rs (642→3 files): extract validation.rs, loader.rs
- Code duplication cleanup: shared components, ~220 lines removed
- Documentation: PROJECT_STRUCTURE.md rewrite, 16 files got //! docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 15:28:11 +03:00

311 lines
10 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Integration tests for input navigation
//!
//! Tests that keyboard navigation actually works through main_input handler
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::methods::messages::MessageMethods;
use tele_tui::input::handle_main_input;
fn key(code: KeyCode) -> KeyEvent {
KeyEvent::new(code, KeyModifiers::empty())
}
/// Test: Стрелки вверх/вниз навигация по списку чатов
#[tokio::test]
async fn test_arrow_navigation_in_chat_list() {
let mut app = TestAppBuilder::new()
.with_chats(vec![
create_test_chat("Chat 1", 101),
create_test_chat("Chat 2", 102),
create_test_chat("Chat 3", 103),
])
.build();
// Начинаем с первого чата (индекс 0)
assert_eq!(app.chat_list_state.selected(), Some(0));
// Down - переходим на второй чат
handle_main_input(&mut app, key(KeyCode::Down)).await;
assert_eq!(app.chat_list_state.selected(), Some(1));
// Down - переходим на третий чат
handle_main_input(&mut app, key(KeyCode::Down)).await;
assert_eq!(app.chat_list_state.selected(), Some(2));
// Down - циклим обратно в начало (циклическая навигация)
handle_main_input(&mut app, key(KeyCode::Down)).await;
assert_eq!(app.chat_list_state.selected(), Some(0));
// Up - циклим в конец (циклическая навигация)
handle_main_input(&mut app, key(KeyCode::Up)).await;
assert_eq!(app.chat_list_state.selected(), Some(2));
// Up - на второй чат
handle_main_input(&mut app, key(KeyCode::Up)).await;
assert_eq!(app.chat_list_state.selected(), Some(1));
// Up - на первый чат
handle_main_input(&mut app, key(KeyCode::Up)).await;
assert_eq!(app.chat_list_state.selected(), Some(0));
}
/// Test: Vim-style j/k навигация по списку чатов
#[tokio::test]
async fn test_vim_navigation_in_chat_list() {
let mut app = TestAppBuilder::new()
.with_chats(vec![
create_test_chat("Chat 1", 101),
create_test_chat("Chat 2", 102),
create_test_chat("Chat 3", 103),
])
.build();
assert_eq!(app.chat_list_state.selected(), Some(0));
// j - вниз
handle_main_input(&mut app, key(KeyCode::Char('j'))).await;
assert_eq!(app.chat_list_state.selected(), Some(1));
// j - ещё вниз
handle_main_input(&mut app, key(KeyCode::Char('j'))).await;
assert_eq!(app.chat_list_state.selected(), Some(2));
// k - вверх
handle_main_input(&mut app, key(KeyCode::Char('k'))).await;
assert_eq!(app.chat_list_state.selected(), Some(1));
// k - ещё вверх
handle_main_input(&mut app, key(KeyCode::Char('k'))).await;
assert_eq!(app.chat_list_state.selected(), Some(0));
}
/// Test: Русские клавиши о/р для навигации
#[tokio::test]
async fn test_russian_keyboard_navigation() {
let mut app = TestAppBuilder::new()
.with_chats(vec![
create_test_chat("Chat 1", 101),
create_test_chat("Chat 2", 102),
])
.build();
assert_eq!(app.chat_list_state.selected(), Some(0));
// о (русская j) - вниз
handle_main_input(&mut app, key(KeyCode::Char('о'))).await;
assert_eq!(app.chat_list_state.selected(), Some(1));
// р (русская k) - вверх
handle_main_input(&mut app, key(KeyCode::Char('р'))).await;
assert_eq!(app.chat_list_state.selected(), Some(0));
}
/// Test: Enter открывает чат
#[tokio::test]
async fn test_enter_opens_chat() {
let mut app = TestAppBuilder::new()
.with_chats(vec![
create_test_chat("Chat 1", 101),
create_test_chat("Chat 2", 102),
])
.build();
// Чат не открыт
assert_eq!(app.selected_chat_id, None);
assert_eq!(app.chat_list_state.selected(), Some(0));
// Enter - открываем первый чат
handle_main_input(&mut app, key(KeyCode::Enter)).await;
assert_eq!(app.selected_chat_id, Some(101.into()));
}
/// Test: Esc закрывает чат
#[tokio::test]
async fn test_esc_closes_chat() {
let mut app = TestAppBuilder::new()
.with_chats(vec![create_test_chat("Chat 1", 101)])
.selected_chat(101)
.build();
// Чат открыт
assert_eq!(app.selected_chat_id, Some(101.into()));
// Esc - закрываем чат
handle_main_input(&mut app, key(KeyCode::Esc)).await;
assert_eq!(app.selected_chat_id, None);
}
/// Test: Навигация курсором в поле ввода (Left/Right)
#[tokio::test]
async fn test_cursor_navigation_in_input() {
let mut app = TestAppBuilder::new()
.with_chats(vec![create_test_chat("Chat 1", 101)])
.selected_chat(101)
.build();
// Вводим текст "Hello"
for c in "Hello".chars() {
handle_main_input(&mut app, key(KeyCode::Char(c))).await;
}
assert_eq!(app.message_input, "Hello");
assert_eq!(app.cursor_position, 5); // Курсор в конце
// Left - курсор влево
handle_main_input(&mut app, key(KeyCode::Left)).await;
assert_eq!(app.cursor_position, 4);
// Left - ещё влево
handle_main_input(&mut app, key(KeyCode::Left)).await;
assert_eq!(app.cursor_position, 3);
// Right - курсор вправо
handle_main_input(&mut app, key(KeyCode::Right)).await;
assert_eq!(app.cursor_position, 4);
// Right - ещё вправо
handle_main_input(&mut app, key(KeyCode::Right)).await;
assert_eq!(app.cursor_position, 5);
// Right - на границе (не выходим за пределы)
handle_main_input(&mut app, key(KeyCode::Right)).await;
assert_eq!(app.cursor_position, 5);
}
/// Test: Home/End навигация в поле ввода
#[tokio::test]
async fn test_home_end_in_input() {
let mut app = TestAppBuilder::new()
.with_chats(vec![create_test_chat("Chat 1", 101)])
.selected_chat(101)
.build();
// Вводим текст
for c in "Hello World".chars() {
handle_main_input(&mut app, key(KeyCode::Char(c))).await;
}
assert_eq!(app.cursor_position, 11);
// Home - в начало
handle_main_input(&mut app, key(KeyCode::Home)).await;
assert_eq!(app.cursor_position, 0);
// End - в конец
handle_main_input(&mut app, key(KeyCode::End)).await;
assert_eq!(app.cursor_position, 11);
}
/// Test: Backspace удаляет символ перед курсором
#[tokio::test]
async fn test_backspace_with_cursor() {
let mut app = TestAppBuilder::new()
.with_chats(vec![create_test_chat("Chat 1", 101)])
.selected_chat(101)
.build();
// Вводим "Hello"
for c in "Hello".chars() {
handle_main_input(&mut app, key(KeyCode::Char(c))).await;
}
assert_eq!(app.message_input, "Hello");
assert_eq!(app.cursor_position, 5);
// Backspace - удаляем "o"
handle_main_input(&mut app, key(KeyCode::Backspace)).await;
assert_eq!(app.message_input, "Hell");
assert_eq!(app.cursor_position, 4);
// Перемещаем курсор в середину (после "e")
handle_main_input(&mut app, key(KeyCode::Left)).await;
handle_main_input(&mut app, key(KeyCode::Left)).await;
assert_eq!(app.cursor_position, 2);
// Backspace - удаляем "e"
handle_main_input(&mut app, key(KeyCode::Backspace)).await;
assert_eq!(app.message_input, "Hll");
assert_eq!(app.cursor_position, 1);
}
/// Test: Ввод символа в середину текста
#[tokio::test]
async fn test_insert_char_at_cursor_position() {
let mut app = TestAppBuilder::new()
.with_chats(vec![create_test_chat("Chat 1", 101)])
.selected_chat(101)
.build();
// Вводим "Hllo"
for c in "Hllo".chars() {
handle_main_input(&mut app, key(KeyCode::Char(c))).await;
}
assert_eq!(app.message_input, "Hllo");
// Курсор на позицию 1 (после "H")
for _ in 0..3 {
handle_main_input(&mut app, key(KeyCode::Left)).await;
}
assert_eq!(app.cursor_position, 1);
// Вставляем "e"
handle_main_input(&mut app, key(KeyCode::Char('e'))).await;
assert_eq!(app.message_input, "Hello");
assert_eq!(app.cursor_position, 2);
}
/// Test: Навигация вверх по сообщениям из пустого инпута
#[tokio::test]
async fn test_up_arrow_selects_last_message_when_input_empty() {
let messages = vec![
TestMessageBuilder::new("Msg 1", 1).outgoing().build(),
TestMessageBuilder::new("Msg 2", 2).outgoing().build(),
TestMessageBuilder::new("Msg 3", 3).outgoing().build(),
];
let mut app = TestAppBuilder::new()
.with_chats(vec![create_test_chat("Chat 1", 101)])
.selected_chat(101)
.with_messages(101, messages)
.build();
// Инпут пустой
assert_eq!(app.message_input, "");
// Up - должен начать выбор сообщения (последнего)
handle_main_input(&mut app, key(KeyCode::Up)).await;
// Проверяем что вошли в режим выбора сообщения
assert!(app.is_selecting_message());
}
/// Test: Циклическая навигация по списку чатов (переход с конца в начало)
#[tokio::test]
async fn test_circular_navigation_optional() {
let mut app = TestAppBuilder::new()
.with_chats(vec![
create_test_chat("Chat 1", 101),
create_test_chat("Chat 2", 102),
])
.build();
// На первом чате
assert_eq!(app.chat_list_state.selected(), Some(0));
// j - на второй чат
handle_main_input(&mut app, key(KeyCode::Char('j'))).await;
assert_eq!(app.chat_list_state.selected(), Some(1));
// j - остаёмся на втором (или циклим в начало, зависит от реализации)
// В текущей реализации должны остаться на месте
handle_main_input(&mut app, key(KeyCode::Char('j'))).await;
// Может быть либо 1 (остались), либо 0 (циклились)
let selected = app.chat_list_state.selected();
assert!(selected == Some(1) || selected == Some(0));
}