// Test App builder use ratatui::widgets::ListState; use std::collections::HashMap; use tele_tui::app::{App, AppScreen, ChatState}; 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, 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, selected_index, }); self } /// Режим ответа на сообщение pub fn replying_to(mut self, message_id: i64) -> Self { self.chat_state = Some(ChatState::Reply { message_id }); self } /// Режим выбора реакции pub fn reaction_picker(mut self, message_id: i64, available_reactions: Vec) -> Self { self.chat_state = Some(ChatState::ReactionPicker { 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 }); 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, 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 /// /// ВАЖНО: Этот метод создаёт 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; // Применяем 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 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, 0) .message_input("Edited text") .build(); assert!(app.is_editing()); assert_eq!(app.chat_state.selected_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"); } }