Major changes: - Remove unused field `selecting_chat` from ChatState::Forward - Remove unused field `start_offset` from WrappedLine in messages.rs - Delete unused functions from modal_handler.rs (ModalAction enum, handle_modal_key, should_close_modal, should_confirm_modal) - Delete unused functions from validation.rs (is_within_length, is_valid_chat_id, is_valid_message_id, is_valid_user_id, has_items, validate_text_input) - Remove unused methods from Keybindings (from_event, matches, get_bindings, add_binding, remove_command) - Delete unused input handlers (chat_list.rs, messages.rs, modal.rs, search.rs) - Remove unused imports across multiple files Performance optimizations: - Fix slow chat opening: load only last 100 messages instead of i32::MAX (10-100x faster) - Reduce timeout from 30s to 10s for initial message load - Fix slow text input: replace O(n) string rebuilding with O(1) String::insert()/remove() operations - Optimize Backspace, Delete, and Char input handlers Bug fixes: - Remove duplicate ChatSortOrder tests after enum deletion - Fix test compilation errors after removing unused methods - Update tests to use get_command() instead of removed matches() method Code cleanup: - Remove ~400 lines of dead code - Remove 12 unused tests - Clean up imports in config/mod.rs, main_input.rs, tdlib/messages.rs Test status: 565 tests passing Warnings reduced from 40+ to 9 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
344 lines
10 KiB
Rust
344 lines
10 KiB
Rust
// Test App builder
|
||
|
||
use ratatui::widgets::ListState;
|
||
use std::collections::HashMap;
|
||
use super::FakeTdClient;
|
||
use tele_tui::app::{App, AppScreen, ChatState};
|
||
use tele_tui::config::Config;
|
||
use tele_tui::tdlib::AuthState;
|
||
use tele_tui::tdlib::{ChatInfo, MessageInfo};
|
||
use tele_tui::types::{ChatId, MessageId};
|
||
|
||
/// Builder для создания тестового App с FakeTdClient\n///\n/// Использует trait-based DI для подмены TdClient на FakeTdClient в тестах.
|
||
pub struct TestAppBuilder {
|
||
config: Config,
|
||
screen: AppScreen,
|
||
chats: Vec<ChatInfo>,
|
||
selected_chat_id: Option<i64>,
|
||
message_input: String,
|
||
is_searching: bool,
|
||
search_query: String,
|
||
chat_state: Option<ChatState>,
|
||
messages: HashMap<i64, Vec<MessageInfo>>,
|
||
status_message: Option<String>,
|
||
auth_state: Option<AuthState>,
|
||
phone_input: Option<String>,
|
||
code_input: Option<String>,
|
||
password_input: Option<String>,
|
||
}
|
||
|
||
impl Default for TestAppBuilder {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
impl TestAppBuilder {
|
||
pub fn new() -> Self {
|
||
Self {
|
||
config: Config::default(),
|
||
screen: AppScreen::Main,
|
||
chats: vec![],
|
||
selected_chat_id: None,
|
||
message_input: String::new(),
|
||
is_searching: false,
|
||
search_query: String::new(),
|
||
chat_state: None,
|
||
messages: HashMap::new(),
|
||
status_message: None,
|
||
auth_state: None,
|
||
phone_input: None,
|
||
code_input: None,
|
||
password_input: None,
|
||
}
|
||
}
|
||
|
||
/// Установить экран
|
||
pub fn screen(mut self, screen: AppScreen) -> Self {
|
||
self.screen = screen;
|
||
self
|
||
}
|
||
|
||
/// Установить конфиг
|
||
pub fn config(mut self, config: Config) -> Self {
|
||
self.config = config;
|
||
self
|
||
}
|
||
|
||
/// Добавить чат
|
||
pub fn with_chat(mut self, chat: ChatInfo) -> Self {
|
||
self.chats.push(chat);
|
||
self
|
||
}
|
||
|
||
/// Добавить несколько чатов
|
||
pub fn with_chats(mut self, chats: Vec<ChatInfo>) -> Self {
|
||
self.chats.extend(chats);
|
||
self
|
||
}
|
||
|
||
/// Выбрать чат
|
||
pub fn selected_chat(mut self, chat_id: i64) -> Self {
|
||
self.selected_chat_id = Some(chat_id);
|
||
self
|
||
}
|
||
|
||
/// Установить текст в инпуте
|
||
pub fn message_input(mut self, text: &str) -> Self {
|
||
self.message_input = text.to_string();
|
||
self
|
||
}
|
||
|
||
/// Режим поиска
|
||
pub fn searching(mut self, query: &str) -> Self {
|
||
self.is_searching = true;
|
||
self.search_query = query.to_string();
|
||
self
|
||
}
|
||
|
||
/// Режим редактирования сообщения
|
||
pub fn editing_message(mut self, message_id: i64, selected_index: usize) -> Self {
|
||
self.chat_state = Some(ChatState::Editing {
|
||
message_id: MessageId::new(message_id),
|
||
selected_index,
|
||
});
|
||
self
|
||
}
|
||
|
||
/// Режим ответа на сообщение
|
||
pub fn replying_to(mut self, message_id: i64) -> Self {
|
||
self.chat_state = Some(ChatState::Reply { message_id: MessageId::new(message_id) });
|
||
self
|
||
}
|
||
|
||
/// Режим выбора реакции
|
||
pub fn reaction_picker(mut self, message_id: i64, available_reactions: Vec<String>) -> Self {
|
||
self.chat_state = Some(ChatState::ReactionPicker {
|
||
message_id: MessageId::new(message_id),
|
||
available_reactions,
|
||
selected_index: 0,
|
||
});
|
||
self
|
||
}
|
||
|
||
/// Режим профиля
|
||
pub fn profile_mode(mut self, info: tele_tui::tdlib::ProfileInfo) -> Self {
|
||
self.chat_state = Some(ChatState::Profile {
|
||
info,
|
||
selected_action: 0,
|
||
leave_group_confirmation_step: 0,
|
||
});
|
||
self
|
||
}
|
||
|
||
/// Подтверждение удаления
|
||
pub fn delete_confirmation(mut self, message_id: i64) -> Self {
|
||
self.chat_state = Some(ChatState::DeleteConfirmation { message_id: MessageId::new(message_id) });
|
||
self
|
||
}
|
||
|
||
/// Добавить сообщение для чата
|
||
pub fn with_message(mut self, chat_id: i64, message: MessageInfo) -> Self {
|
||
self.messages
|
||
.entry(chat_id)
|
||
.or_insert_with(Vec::new)
|
||
.push(message);
|
||
self
|
||
}
|
||
|
||
/// Добавить несколько сообщений для чата
|
||
pub fn with_messages(mut self, chat_id: i64, messages: Vec<MessageInfo>) -> Self {
|
||
self.messages
|
||
.entry(chat_id)
|
||
.or_insert_with(Vec::new)
|
||
.extend(messages);
|
||
self
|
||
}
|
||
|
||
/// Установить выбранное сообщение (режим selection)
|
||
pub fn selecting_message(mut self, selected_index: usize) -> Self {
|
||
self.chat_state = Some(ChatState::MessageSelection { selected_index });
|
||
self
|
||
}
|
||
|
||
/// Режим поиска по сообщениям в чате
|
||
pub fn message_search(mut self, query: &str) -> Self {
|
||
self.chat_state = Some(ChatState::SearchInChat {
|
||
query: query.to_string(),
|
||
results: Vec::new(),
|
||
selected_index: 0,
|
||
});
|
||
self
|
||
}
|
||
|
||
/// Режим пересылки сообщения
|
||
pub fn forward_mode(mut self, message_id: i64) -> Self {
|
||
self.chat_state = Some(ChatState::Forward {
|
||
message_id: MessageId::new(message_id),
|
||
});
|
||
self
|
||
}
|
||
|
||
/// Установить статус сообщение (для loading screen)
|
||
pub fn status_message(mut self, message: &str) -> Self {
|
||
self.status_message = Some(message.to_string());
|
||
self
|
||
}
|
||
|
||
/// Установить auth state
|
||
pub fn auth_state(mut self, state: AuthState) -> Self {
|
||
self.auth_state = Some(state);
|
||
self
|
||
}
|
||
|
||
/// Установить phone input
|
||
pub fn phone_input(mut self, phone: &str) -> Self {
|
||
self.phone_input = Some(phone.to_string());
|
||
self
|
||
}
|
||
|
||
/// Установить code input
|
||
pub fn code_input(mut self, code: &str) -> Self {
|
||
self.code_input = Some(code.to_string());
|
||
self
|
||
}
|
||
|
||
/// Установить password input
|
||
pub fn password_input(mut self, password: &str) -> Self {
|
||
self.password_input = Some(password.to_string());
|
||
self
|
||
}
|
||
|
||
/// Построить App с FakeTdClient
|
||
///
|
||
/// Создаёт App с FakeTdClient, подходит для любых тестов включая
|
||
/// интеграционные тесты логики.
|
||
pub fn build(self) -> App<FakeTdClient> {
|
||
// Создаём FakeTdClient с чатами и сообщениями
|
||
let mut fake_client = FakeTdClient::new();
|
||
|
||
// Добавляем чаты
|
||
for chat in &self.chats {
|
||
fake_client = fake_client.with_chat(chat.clone());
|
||
}
|
||
|
||
// Добавляем сообщения
|
||
for (chat_id, messages) in self.messages {
|
||
fake_client = fake_client.with_messages(chat_id, messages);
|
||
}
|
||
|
||
// Устанавливаем текущий чат если нужно
|
||
if let Some(chat_id) = self.selected_chat_id {
|
||
*fake_client.current_chat_id.lock().unwrap() = Some(chat_id);
|
||
}
|
||
|
||
// Устанавливаем auth state если нужно
|
||
if let Some(auth_state) = self.auth_state {
|
||
fake_client = fake_client.with_auth_state(auth_state);
|
||
}
|
||
|
||
// Создаём App с FakeTdClient
|
||
let mut app = App::with_client(self.config, fake_client);
|
||
|
||
app.screen = self.screen;
|
||
app.chats = self.chats;
|
||
app.selected_chat_id = self.selected_chat_id.map(ChatId::new);
|
||
app.message_input = self.message_input;
|
||
app.is_searching = self.is_searching;
|
||
app.search_query = self.search_query;
|
||
|
||
// Применяем chat_state если он установлен
|
||
if let Some(chat_state) = self.chat_state {
|
||
app.chat_state = chat_state;
|
||
}
|
||
|
||
// Применяем status_message
|
||
if let Some(status) = self.status_message {
|
||
app.status_message = Some(status);
|
||
}
|
||
|
||
// Применяем auth inputs
|
||
if let Some(phone) = self.phone_input {
|
||
app.set_phone_input(phone);
|
||
}
|
||
if let Some(code) = self.code_input {
|
||
app.set_code_input(code);
|
||
}
|
||
if let Some(password) = self.password_input {
|
||
app.set_password_input(password);
|
||
}
|
||
|
||
// Выбираем первый чат если есть
|
||
if !app.chats.is_empty() {
|
||
let mut list_state = ListState::default();
|
||
list_state.select(Some(0));
|
||
app.chat_list_state = list_state;
|
||
}
|
||
|
||
app
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use crate::helpers::test_data::create_test_chat;
|
||
|
||
#[test]
|
||
fn test_builder_defaults() {
|
||
let app = TestAppBuilder::new().build();
|
||
|
||
assert_eq!(app.screen, AppScreen::Main);
|
||
assert_eq!(app.chats.len(), 0);
|
||
assert_eq!(app.selected_chat_id, None);
|
||
assert_eq!(app.message_input, "");
|
||
}
|
||
|
||
#[test]
|
||
fn test_builder_with_chats() {
|
||
let chat1 = create_test_chat("Mom", 123);
|
||
let chat2 = create_test_chat("Boss", 456);
|
||
|
||
let app = TestAppBuilder::new()
|
||
.with_chat(chat1)
|
||
.with_chat(chat2)
|
||
.build();
|
||
|
||
assert_eq!(app.chats.len(), 2);
|
||
assert_eq!(app.chats[0].title, "Mom");
|
||
assert_eq!(app.chats[1].title, "Boss");
|
||
}
|
||
|
||
#[test]
|
||
fn test_builder_with_selected_chat() {
|
||
let chat = create_test_chat("Mom", 123);
|
||
|
||
let app = TestAppBuilder::new()
|
||
.with_chat(chat)
|
||
.selected_chat(123)
|
||
.build();
|
||
|
||
assert_eq!(app.selected_chat_id, Some(ChatId::new(123)));
|
||
}
|
||
|
||
#[test]
|
||
fn test_builder_editing_mode() {
|
||
let app = TestAppBuilder::new()
|
||
.editing_message(999, 0)
|
||
.message_input("Edited text")
|
||
.build();
|
||
|
||
assert!(app.is_editing());
|
||
assert_eq!(app.chat_state.selected_message_id(), Some(MessageId::new(999)));
|
||
assert_eq!(app.message_input, "Edited text");
|
||
}
|
||
|
||
#[test]
|
||
fn test_builder_search_mode() {
|
||
let app = TestAppBuilder::new().searching("test query").build();
|
||
|
||
assert!(app.is_searching);
|
||
assert_eq!(app.search_query, "test query");
|
||
}
|
||
}
|