// 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, selected_chat_id: Option, message_input: String, is_searching: bool, search_query: String, chat_state: Option, messages: HashMap>, 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(), 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) -> 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) -> 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) -> 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), selecting_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 с FakeTdClient /// /// Создаёт App с FakeTdClient, подходит для любых тестов включая /// интеграционные тесты логики. pub fn build(self) -> App { // Создаём 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.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; } 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"); } }