Some checks failed
CI / Check (pull_request) Has been cancelled
CI / Format (pull_request) Has been cancelled
CI / Clippy (pull_request) Has been cancelled
CI / Build (macos-latest) (pull_request) Has been cancelled
CI / Build (ubuntu-latest) (pull_request) Has been cancelled
CI / Build (windows-latest) (pull_request) Has been cancelled
340 lines
11 KiB
Rust
340 lines
11 KiB
Rust
// Test App builder
|
||
|
||
use tele_tui::app::{App, AppScreen};
|
||
use tele_tui::config::Config;
|
||
use tele_tui::tdlib::{ChatInfo, MessageInfo};
|
||
use tele_tui::tdlib::client::AuthState;
|
||
use ratatui::widgets::ListState;
|
||
use std::collections::HashMap;
|
||
|
||
/// Builder для создания тестового App
|
||
///
|
||
/// Примечание: Так как App содержит реальный TdClient,
|
||
/// этот билдер подходит только для UI/snapshot тестов.
|
||
/// Для интеграционных тестов логики понадобится рефакторинг
|
||
/// с выделением trait для TdClient.
|
||
pub struct TestAppBuilder {
|
||
config: Config,
|
||
screen: AppScreen,
|
||
chats: Vec<ChatInfo>,
|
||
selected_chat_id: Option<i64>,
|
||
message_input: String,
|
||
is_searching: bool,
|
||
search_query: String,
|
||
editing_message_id: Option<i64>,
|
||
replying_to_message_id: Option<i64>,
|
||
is_reaction_picker_mode: bool,
|
||
is_profile_mode: bool,
|
||
confirm_delete_message_id: Option<i64>,
|
||
messages: HashMap<i64, Vec<MessageInfo>>,
|
||
selected_message_index: Option<usize>,
|
||
message_search_mode: bool,
|
||
message_search_query: String,
|
||
forwarding_message_id: Option<i64>,
|
||
is_selecting_forward_chat: bool,
|
||
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(),
|
||
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<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) -> 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<MessageInfo>) -> 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");
|
||
}
|
||
}
|