Files
telegram-tui/tests/chat_list.rs
Mikhail Kilin 222a21770c fix: implement chunked message loading to fill screen on chat open
Проблема:
- get_chat_history() загружала только один чанк (50 сообщений max)
- При запросе 100 сообщений возвращалось только 50
- Экран не заполнялся полностью при открытии чата

Решение:
- Добавлена чанковая загрузка по TDLIB_MESSAGE_LIMIT (50) сообщений
- Автоматическая подгрузка пока не достигнут запрошенный limit
- Правильная сборка сообщений (старые чанки вставляются в начало)
- Retry логика для каждого чанка (до 3 попыток)

Изменения в src/tdlib/messages.rs:
- get_chat_history(): цикл загрузки чанков вместо одного запроса
- Вставка более старых чанков в начало списка (splice)
- Обработка edge cases (пустые результаты, ошибки, конец истории)

Тесты:
- test_chat_history_chunked_loading: проверка загрузки 100, 120, 200 сообщений
- Проверка правильного порядка сообщений (от старых к новым)
- Проверка границы между чанками (messages 50/51)

Все тесты пройдены: 343/343 

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-04 02:48:30 +03:00

416 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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<MessageId> = 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()
);
}
#[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);
}