// Test App builder use ratatui::widgets::ListState; use std::collections::HashMap; use tele_tui::app::{App, AppScreen}; use tele_tui::config::Config; use tele_tui::tdlib::client::AuthState; use tele_tui::tdlib::{ChatInfo, MessageInfo}; /// Builder для создания тестового App /// /// Примечание: Так как App содержит реальный TdClient, /// этот билдер подходит только для UI/snapshot тестов. /// Для интеграционных тестов логики понадобится рефакторинг /// с выделением trait для TdClient. pub struct TestAppBuilder { config: Config, screen: AppScreen, chats: Vec, selected_chat_id: Option, message_input: String, is_searching: bool, search_query: String, editing_message_id: Option, replying_to_message_id: Option, is_reaction_picker_mode: bool, is_profile_mode: bool, confirm_delete_message_id: Option, messages: HashMap>, selected_message_index: Option, message_search_mode: bool, message_search_query: String, forwarding_message_id: Option, is_selecting_forward_chat: bool, status_message: Option, auth_state: Option, phone_input: Option, code_input: Option, password_input: Option, } 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(), editing_message_id: None, replying_to_message_id: None, is_reaction_picker_mode: false, is_profile_mode: false, confirm_delete_message_id: None, messages: HashMap::new(), selected_message_index: None, message_search_mode: false, message_search_query: String::new(), forwarding_message_id: None, is_selecting_forward_chat: false, 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) -> 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) -> Self { self.editing_message_id = Some(message_id); self } /// Режим ответа на сообщение pub fn replying_to(mut self, message_id: i64) -> Self { self.replying_to_message_id = Some(message_id); self } /// Режим выбора реакции pub fn reaction_picker(mut self) -> Self { self.is_reaction_picker_mode = true; self } /// Режим профиля pub fn profile_mode(mut self) -> Self { self.is_profile_mode = true; self } /// Подтверждение удаления pub fn delete_confirmation(mut self, message_id: i64) -> Self { self.confirm_delete_message_id = Some(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) -> Self { self.messages .entry(chat_id) .or_insert_with(Vec::new) .extend(messages); self } /// Установить выбранное сообщение (режим selection) pub fn selecting_message(mut self, message_index: usize) -> Self { self.selected_message_index = Some(message_index); self } /// Режим поиска по сообщениям в чате pub fn message_search(mut self, query: &str) -> Self { self.message_search_mode = true; self.message_search_query = query.to_string(); self } /// Режим пересылки сообщения pub fn forward_mode(mut self, message_id: i64) -> Self { self.forwarding_message_id = Some(message_id); self.is_selecting_forward_chat = true; 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 /// /// ВАЖНО: Этот метод создаёт App с реальным TdClient, /// поэтому он подходит только для UI тестов, где мы /// не вызываем методы TdClient. pub fn build(self) -> App { let mut app = App::new(self.config); app.screen = self.screen; app.chats = self.chats; app.selected_chat_id = self.selected_chat_id; app.message_input = self.message_input; app.is_searching = self.is_searching; app.search_query = self.search_query; app.editing_message_id = self.editing_message_id; app.replying_to_message_id = self.replying_to_message_id; app.is_reaction_picker_mode = self.is_reaction_picker_mode; app.is_profile_mode = self.is_profile_mode; app.confirm_delete_message_id = self.confirm_delete_message_id; app.selected_message_index = self.selected_message_index; app.is_message_search_mode = self.message_search_mode; app.message_search_query = self.message_search_query; app.forwarding_message_id = self.forwarding_message_id; app.is_selecting_forward_chat = self.is_selecting_forward_chat; // Применяем status_message if let Some(status) = self.status_message { app.status_message = Some(status); } // Применяем auth state if let Some(auth_state) = self.auth_state { app.td_client.auth_state = auth_state; } // Применяем auth inputs if let Some(phone) = self.phone_input { app.phone_input = phone; } if let Some(code) = self.code_input { app.code_input = code; } if let Some(password) = self.password_input { app.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; } // Применяем сообщения к текущему открытому чату if let Some(chat_id) = self.selected_chat_id { if let Some(messages) = self.messages.get(&chat_id) { app.td_client.current_chat_messages = messages.clone(); app.td_client.current_chat_id = Some(chat_id); } } 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(123)); } #[test] fn test_builder_editing_mode() { let app = TestAppBuilder::new() .editing_message(999) .message_input("Edited text") .build(); assert_eq!(app.editing_message_id, Some(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"); } }