// Chat list UI snapshot tests mod helpers; use helpers::app_builder::TestAppBuilder; use helpers::snapshot_utils::{buffer_to_string, render_to_buffer}; use helpers::test_data::{create_test_chat, TestChatBuilder, TestMessageBuilder}; use insta::assert_snapshot; #[test] fn snapshot_empty_chat_list() { let mut app = TestAppBuilder::new().build(); let buffer = render_to_buffer(80, 24, |f| { tele_tui::ui::chat_list::render(f, f.area(), &mut app); }); let output = buffer_to_string(&buffer); assert_snapshot!("empty_chat_list", output); } #[test] fn snapshot_chat_list_with_three_chats() { let chat1 = create_test_chat("Mom", 123); let chat2 = create_test_chat("Boss", 456); let chat3 = create_test_chat("Rust Community", 789); let mut app = TestAppBuilder::new() .with_chats(vec![chat1, chat2, chat3]) .build(); let buffer = render_to_buffer(80, 24, |f| { tele_tui::ui::chat_list::render(f, f.area(), &mut app); }); let output = buffer_to_string(&buffer); assert_snapshot!("chat_list_three_chats", output); } #[test] fn snapshot_chat_with_unread_count() { let chat = TestChatBuilder::new("Mom", 123) .unread_count(5) .last_message("Привет, как дела?") .build(); let mut app = TestAppBuilder::new().with_chat(chat).build(); let buffer = render_to_buffer(80, 24, |f| { tele_tui::ui::chat_list::render(f, f.area(), &mut app); }); let output = buffer_to_string(&buffer); assert_snapshot!("chat_with_unread", output); } #[test] fn test_incoming_message_shows_unread_badge() { use tele_tui::tdlib::ChatInfo; use tele_tui::types::ChatId; // Создаём чат БЕЗ непрочитанных сообщений let chat = TestChatBuilder::new("Friend", 999) .unread_count(0) .last_message("Как дела?") .build(); let mut app = TestAppBuilder::new().with_chat(chat).build(); // Рендерим UI - должно быть без "(1)" let buffer_before = render_to_buffer(80, 24, |f| { tele_tui::ui::chat_list::render(f, f.area(), &mut app); }); let output_before = buffer_to_string(&buffer_before); // Проверяем что нет "(1)" в первой строке чата assert!(!output_before.contains("(1)"), "Before: should not contain (1)"); // Симулируем входящее сообщение - обновляем unread_count app.chats[0].unread_count = 1; app.chats[0].last_message = "Привет!".to_string(); // Рендерим UI снова - теперь должно быть "(1)" let buffer_after = render_to_buffer(80, 24, |f| { tele_tui::ui::chat_list::render(f, f.area(), &mut app); }); let output_after = buffer_to_string(&buffer_after); // Проверяем что появилось "(1)" в первой строке чата assert!( output_after.contains("(1)"), "After: should contain (1)\nActual output:\n{}", output_after ); } #[tokio::test] async fn test_opening_chat_clears_unread_badge() { use helpers::test_data::TestMessageBuilder; use tele_tui::tdlib::TdClientTrait; use tele_tui::types::{ChatId, MessageId}; // Создаём чат с 3 непрочитанными сообщениями let chat = TestChatBuilder::new("Friend", 999) .unread_count(3) .last_message("У тебя 3 новых сообщения") .build(); // Создаём 3 входящих сообщения (по умолчанию is_outgoing = false) let messages = vec![ TestMessageBuilder::new("Привет!", 1) .sender("Friend") .build(), TestMessageBuilder::new("Как дела?", 2) .sender("Friend") .build(), TestMessageBuilder::new("Ответь мне!", 3) .sender("Friend") .build(), ]; let mut app = TestAppBuilder::new() .with_chat(chat) .with_messages(999, messages) .build(); // Рендерим UI - должно быть "(3)" let buffer_before = render_to_buffer(80, 24, |f| { tele_tui::ui::chat_list::render(f, f.area(), &mut app); }); let output_before = buffer_to_string(&buffer_before); // Проверяем что есть "(3)" в списке чатов assert!( output_before.contains("(3)"), "Before opening: should contain (3)\nActual output:\n{}", output_before ); // Симулируем открытие чата - загружаем историю let chat_id = ChatId::new(999); let loaded_messages = app.td_client.get_chat_history(chat_id, 100).await.unwrap(); // Собираем ID входящих сообщений (как в реальном коде) let incoming_message_ids: Vec = loaded_messages .iter() .filter(|msg| !msg.is_outgoing()) .map(|msg| msg.id()) .collect(); // Проверяем что нашли 3 входящих сообщения assert_eq!(incoming_message_ids.len(), 3, "Should have 3 incoming messages"); // Добавляем в очередь для отметки как прочитанные (напрямую через Mutex) app.td_client .pending_view_messages .lock() .unwrap() .push((chat_id, incoming_message_ids)); // Обрабатываем очередь (как в main loop) app.td_client.process_pending_view_messages().await; // В FakeTdClient это должно записаться в viewed_messages let viewed = app.td_client.get_viewed_messages(); assert_eq!(viewed.len(), 1, "Should have one batch of viewed messages"); assert_eq!(viewed[0].0, 999, "Should be for chat 999"); assert_eq!(viewed[0].1.len(), 3, "Should have viewed 3 messages"); // В реальном приложении TDLib отправит Update::ChatReadInbox // который обновит unread_count в чате. Симулируем это: app.chats[0].unread_count = 0; // Рендерим UI снова - "(3)" должно пропасть let buffer_after = render_to_buffer(80, 24, |f| { tele_tui::ui::chat_list::render(f, f.area(), &mut app); }); let output_after = buffer_to_string(&buffer_after); // Проверяем что "(3)" больше нет assert!( !output_after.contains("(3)"), "After opening: should not contain (3)\nActual output:\n{}", output_after ); } #[tokio::test] async fn test_opening_chat_loads_many_messages() { use helpers::test_data::TestMessageBuilder; use tele_tui::tdlib::TdClientTrait; use tele_tui::types::ChatId; // Создаём чат с 50 сообщениями let chat = TestChatBuilder::new("History Chat", 888) .last_message("Message 50") .build(); // Создаём 50 сообщений let messages: Vec<_> = (1..=50) .map(|i| { TestMessageBuilder::new(&format!("Message {}", i), i) .sender("Friend") .build() }) .collect(); let mut app = TestAppBuilder::new() .with_chat(chat) .with_messages(888, messages) .build(); // Открываем чат - загружаем историю (запрашиваем 100 сообщений) let chat_id = ChatId::new(888); let loaded_messages = app.td_client.get_chat_history(chat_id, 100).await.unwrap(); // Проверяем что загрузились ВСЕ 50 сообщений, а не только последние 2-3 assert_eq!( loaded_messages.len(), 50, "Should load all 50 messages, not just last few. Got: {}", loaded_messages.len() ); // Проверяем что сообщения в правильном порядке (от старых к новым) assert_eq!(loaded_messages[0].text(), "Message 1"); assert_eq!(loaded_messages[24].text(), "Message 25"); assert_eq!(loaded_messages[49].text(), "Message 50"); } #[tokio::test] async fn test_chat_history_chunked_loading() { use tele_tui::tdlib::TdClientTrait; use tele_tui::types::ChatId; // Создаём чат с 120 сообщениями (больше чем TDLIB_MESSAGE_LIMIT = 50) let chat = TestChatBuilder::new("Long History Chat", 999) .last_message("Message 120") .build(); // Создаём 120 сообщений let messages: Vec<_> = (1..=120) .map(|i| { TestMessageBuilder::new(&format!("Message {}", i), i) .sender("Friend") .build() }) .collect(); let mut app = TestAppBuilder::new() .with_chat(chat) .with_messages(999, messages) .build(); // Тест 1: Загружаем 100 сообщений (больше чем 50, меньше чем 120) let chat_id = ChatId::new(999); let loaded_messages = app.td_client.get_chat_history(chat_id, 100).await.unwrap(); assert_eq!( loaded_messages.len(), 100, "Should load 100 messages with chunked loading. Got: {}", loaded_messages.len() ); // Проверяем что сообщения в правильном порядке (от старых к новым) assert_eq!(loaded_messages[0].text(), "Message 1"); assert_eq!(loaded_messages[49].text(), "Message 50"); // Граница первого чанка assert_eq!(loaded_messages[50].text(), "Message 51"); // Начало второго чанка assert_eq!(loaded_messages[99].text(), "Message 100"); // Тест 2: Загружаем все 120 сообщений let all_messages = app.td_client.get_chat_history(chat_id, 120).await.unwrap(); assert_eq!( all_messages.len(), 120, "Should load all 120 messages. Got: {}", all_messages.len() ); assert_eq!(all_messages[0].text(), "Message 1"); assert_eq!(all_messages[119].text(), "Message 120"); // Тест 3: Запрашиваем 200 сообщений, но есть только 120 let limited_messages = app.td_client.get_chat_history(chat_id, 200).await.unwrap(); assert_eq!( limited_messages.len(), 120, "Should load only available 120 messages when requesting 200. Got: {}", limited_messages.len() ); } #[tokio::test] async fn test_chat_history_loads_all_without_limit() { use tele_tui::tdlib::TdClientTrait; use tele_tui::types::ChatId; // Создаём чат с 200 сообщениями (4 чанка по 50) let chat = TestChatBuilder::new("Very Long Chat", 1001) .last_message("Message 200") .build(); let messages: Vec<_> = (1..=200) .map(|i| { TestMessageBuilder::new(&format!("Msg {}", i), i) .sender("User") .build() }) .collect(); let mut app = TestAppBuilder::new() .with_chat(chat) .with_messages(1001, messages) .build(); // Загружаем без лимита (i32::MAX) let chat_id = ChatId::new(1001); let all = app .td_client .get_chat_history(chat_id, i32::MAX) .await .unwrap(); assert_eq!(all.len(), 200, "Should load all 200 messages without limit"); assert_eq!(all[0].text(), "Msg 1", "First message should be oldest"); assert_eq!(all[199].text(), "Msg 200", "Last message should be newest"); } #[tokio::test] async fn test_load_older_messages_pagination() { use tele_tui::tdlib::TdClientTrait; use tele_tui::types::{ChatId, MessageId}; // Создаём чат со 150 сообщениями let chat = TestChatBuilder::new("Paginated Chat", 1002) .last_message("Message 150") .build(); let messages: Vec<_> = (1..=150) .map(|i| { TestMessageBuilder::new(&format!("Msg {}", i), i) .sender("User") .build() }) .collect(); let mut app = TestAppBuilder::new() .with_chat(chat) .with_messages(1002, messages) .build(); let chat_id = ChatId::new(1002); // Шаг 1: Загружаем только последние 30 сообщений // get_chat_history загружает от конца, поэтому получим сообщения 1-30 let initial_batch = app.td_client.get_chat_history(chat_id, 30).await.unwrap(); assert_eq!(initial_batch.len(), 30, "Should load 30 messages initially"); assert_eq!(initial_batch[0].text(), "Msg 1", "First message should be Msg 1"); assert_eq!(initial_batch[29].text(), "Msg 30", "Last should be Msg 30"); // Шаг 2: Загружаем все 150 сообщений для проверки load_older let all_messages = app.td_client.get_chat_history(chat_id, 150).await.unwrap(); assert_eq!(all_messages.len(), 150); // Имитируем ситуацию: у нас есть сообщения 101-150, хотим загрузить 51-100 // Берем ID сообщения 101 (первое в нашем "окне") let msg_101_id = all_messages[100].id(); // index 100 = Msg 101 // Загружаем сообщения старше 101 let older_batch = app .td_client .load_older_messages(chat_id, msg_101_id) .await .unwrap(); // Должны получить сообщения 1-100 (все что старше 101) assert_eq!(older_batch.len(), 100, "Should load 100 older messages"); assert_eq!(older_batch[0].text(), "Msg 1", "Oldest should be Msg 1"); assert_eq!(older_batch[99].text(), "Msg 100", "Newest in batch should be Msg 100"); } #[test] fn snapshot_chat_with_pinned() { let chat = TestChatBuilder::new("Important Chat", 123) .pinned() .last_message("Pinned message") .build(); let mut app = TestAppBuilder::new().with_chat(chat).build(); let buffer = render_to_buffer(80, 24, |f| { tele_tui::ui::chat_list::render(f, f.area(), &mut app); }); let output = buffer_to_string(&buffer); assert_snapshot!("chat_pinned", output); } #[test] fn snapshot_chat_with_muted() { let chat = TestChatBuilder::new("Spam Group", 123) .muted() .unread_count(99) .last_message("Too many messages") .build(); let mut app = TestAppBuilder::new().with_chat(chat).build(); let buffer = render_to_buffer(80, 24, |f| { tele_tui::ui::chat_list::render(f, f.area(), &mut app); }); let output = buffer_to_string(&buffer); assert_snapshot!("chat_muted", output); } #[test] fn snapshot_chat_with_mentions() { let chat = TestChatBuilder::new("Work Group", 123) .unread_count(10) .unread_mentions(2) .last_message("@me check this out") .build(); let mut app = TestAppBuilder::new().with_chat(chat).build(); let buffer = render_to_buffer(80, 24, |f| { tele_tui::ui::chat_list::render(f, f.area(), &mut app); }); let output = buffer_to_string(&buffer); assert_snapshot!("chat_with_mentions", output); } #[test] fn snapshot_selected_chat() { let chat1 = create_test_chat("Mom", 123); let chat2 = create_test_chat("Boss", 456); let mut app = TestAppBuilder::new() .with_chats(vec![chat1, chat2]) .selected_chat(123) .build(); let buffer = render_to_buffer(80, 24, |f| { tele_tui::ui::chat_list::render(f, f.area(), &mut app); }); let output = buffer_to_string(&buffer); assert_snapshot!("chat_selected", output); } #[test] fn snapshot_chat_long_title() { let chat = TestChatBuilder::new("Very Long Chat Title That Should Be Truncated", 123) .last_message("Test message") .build(); let mut app = TestAppBuilder::new().with_chat(chat).build(); let buffer = render_to_buffer(80, 24, |f| { tele_tui::ui::chat_list::render(f, f.area(), &mut app); }); let output = buffer_to_string(&buffer); assert_snapshot!("chat_long_title", output); } #[test] fn snapshot_chat_search_mode() { let chat1 = create_test_chat("Mom", 123); let chat2 = create_test_chat("Boss", 456); let chat3 = create_test_chat("Rust Community", 789); let mut app = TestAppBuilder::new() .with_chats(vec![chat1, chat2, chat3]) .searching("Mom") .build(); let buffer = render_to_buffer(80, 24, |f| { tele_tui::ui::chat_list::render(f, f.area(), &mut app); }); let output = buffer_to_string(&buffer); assert_snapshot!("chat_list_search_mode", output); } #[test] fn snapshot_chat_with_online_status() { use tele_tui::tdlib::UserOnlineStatus; use tele_tui::types::ChatId; let chat = TestChatBuilder::new("Alice", 123) .last_message("Hey there!") .build(); let mut app = TestAppBuilder::new() .with_chat(chat) .selected_chat(123) .build(); // Note: Online status setup removed due to trait-based DI // User status is not critical for this UI snapshot test let buffer = render_to_buffer(80, 24, |f| { tele_tui::ui::chat_list::render(f, f.area(), &mut app); }); let output = buffer_to_string(&buffer); assert_snapshot!("chat_with_online_status", output); }