Сгруппированы 16 плоских полей MessageInfo в 4 логические структуры для улучшения организации кода и maintainability. Новые структуры: - MessageMetadata: id, sender_name, date, edit_date - MessageContent: text, entities - MessageState: is_outgoing, is_read, can_be_edited, can_be_deleted_* - MessageInteractions: reply_to, forward_from, reactions Изменения: - Добавлены 4 новые структуры в tdlib/types.rs - Обновлена MessageInfo для использования новых структур - Добавлен конструктор MessageInfo::new() для удобного создания - Добавлены getter методы (id(), text(), sender_name() и др.) для удобного доступа - Обновлены все места создания MessageInfo (convert_message) - Обновлены все места использования (~200+ обращений): * ui/messages.rs: рендеринг сообщений * app/mod.rs: логика приложения * input/main_input.rs: обработка ввода и копирование * tdlib/client.rs: обработка updates * Все тестовые файлы (14 файлов) Преимущества: - Логическая группировка данных - Проще понимать структуру сообщения - Легче добавлять новые поля в будущем - Улучшенная читаемость кода Статус: Priority 2 теперь 80% (4/5 задач) - ✅ Error enum - ✅ Config validation - ✅ Newtype для ID - ✅ MessageInfo реструктуризация - ⏳ MessageBuilder pattern Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
238 lines
7.4 KiB
Rust
238 lines
7.4 KiB
Rust
// Integration tests for search flow
|
||
|
||
mod helpers;
|
||
|
||
use helpers::fake_tdclient::FakeTdClient;
|
||
use helpers::test_data::{create_test_chat, TestChatBuilder, TestMessageBuilder};
|
||
|
||
/// Test: Поиск по чатам фильтрует по названию
|
||
#[test]
|
||
fn test_search_chats_by_title() {
|
||
let mut client = FakeTdClient::new();
|
||
|
||
let chat1 = create_test_chat("Mom", 123);
|
||
let chat2 = create_test_chat("Boss", 456);
|
||
let chat3 = create_test_chat("Mom's Work", 789);
|
||
|
||
client = client.with_chats(vec![chat1, chat2, chat3]);
|
||
|
||
// Ищем "mom" - должно найти "Mom" и "Mom's Work"
|
||
let query = "mom".to_lowercase();
|
||
let filtered: Vec<_> = client
|
||
.get_chats()
|
||
.iter()
|
||
.filter(|c| c.title.to_lowercase().contains(&query))
|
||
.collect();
|
||
|
||
assert_eq!(filtered.len(), 2);
|
||
assert_eq!(filtered[0].title, "Mom");
|
||
assert_eq!(filtered[1].title, "Mom's Work");
|
||
}
|
||
|
||
/// Test: Поиск по чатам фильтрует по @username
|
||
#[test]
|
||
fn test_search_chats_by_username() {
|
||
let mut client = FakeTdClient::new();
|
||
|
||
let chat1 = TestChatBuilder::new("Alice", 123).username("alice").build();
|
||
|
||
let chat2 = TestChatBuilder::new("Bob", 456).username("bobby").build();
|
||
|
||
let chat3 = TestChatBuilder::new("Charlie", 789).build(); // Без username
|
||
|
||
client = client.with_chats(vec![chat1, chat2, chat3]);
|
||
|
||
// Ищем "bob" - должно найти "Bob" (@bobby)
|
||
let query = "bob".to_lowercase();
|
||
let filtered: Vec<_> = client
|
||
.get_chats()
|
||
.iter()
|
||
.filter(|c| {
|
||
c.title.to_lowercase().contains(&query)
|
||
|| c.username
|
||
.as_ref()
|
||
.map(|u| u.to_lowercase().contains(&query))
|
||
.unwrap_or(false)
|
||
})
|
||
.collect();
|
||
|
||
assert_eq!(filtered.len(), 1);
|
||
assert_eq!(filtered[0].title, "Bob");
|
||
}
|
||
|
||
/// Test: Пустой поисковый запрос возвращает все чаты
|
||
#[test]
|
||
fn test_search_empty_query_returns_all() {
|
||
let mut client = FakeTdClient::new();
|
||
|
||
let chat1 = create_test_chat("Mom", 123);
|
||
let chat2 = create_test_chat("Boss", 456);
|
||
let chat3 = create_test_chat("Friend", 789);
|
||
|
||
client = client.with_chats(vec![chat1, chat2, chat3]);
|
||
|
||
// Пустой запрос
|
||
let query = "";
|
||
let filtered: Vec<_> = client
|
||
.get_chats()
|
||
.iter()
|
||
.filter(|c| c.title.to_lowercase().contains(query))
|
||
.collect();
|
||
|
||
// Все чаты проходят фильтр (пустая строка содержится в любой строке)
|
||
assert_eq!(filtered.len(), 3);
|
||
}
|
||
|
||
/// Test: Поиск внутри чата по тексту сообщений
|
||
#[test]
|
||
fn test_search_messages_in_chat() {
|
||
let mut client = FakeTdClient::new();
|
||
|
||
let msg1 = TestMessageBuilder::new("Hello world", 100).build();
|
||
let msg2 = TestMessageBuilder::new("How are you?", 101).build();
|
||
let msg3 = TestMessageBuilder::new("Hello again", 102).build();
|
||
|
||
client = client.with_messages(123, vec![msg1, msg2, msg3]);
|
||
|
||
// Ищем "hello"
|
||
let query = "hello".to_lowercase();
|
||
let messages = client.get_messages(123);
|
||
let found: Vec<_> = messages
|
||
.iter()
|
||
.filter(|m| m.text().to_lowercase().contains(&query))
|
||
.collect();
|
||
|
||
assert_eq!(found.len(), 2);
|
||
assert_eq!(found[0].text(), "Hello world");
|
||
assert_eq!(found[1].text(), "Hello again");
|
||
}
|
||
|
||
/// Test: Навигация по результатам поиска (n/N)
|
||
#[test]
|
||
fn test_navigate_search_results() {
|
||
let mut client = FakeTdClient::new();
|
||
|
||
let msg1 = TestMessageBuilder::new("First match", 100).build();
|
||
let msg2 = TestMessageBuilder::new("Second match", 101).build();
|
||
let msg3 = TestMessageBuilder::new("Third match", 102).build();
|
||
|
||
client = client.with_messages(123, vec![msg1, msg2, msg3]);
|
||
|
||
// Ищем "match"
|
||
let query = "match".to_lowercase();
|
||
let messages = client.get_messages(123);
|
||
let results: Vec<_> = messages
|
||
.iter()
|
||
.enumerate()
|
||
.filter(|(_, m)| m.text().to_lowercase().contains(&query))
|
||
.collect();
|
||
|
||
assert_eq!(results.len(), 3);
|
||
|
||
// Навигация: начинаем с индекса 0
|
||
let mut current_index = 0;
|
||
|
||
// n - следующий результат
|
||
current_index = (current_index + 1) % results.len();
|
||
assert_eq!(current_index, 1);
|
||
assert_eq!(results[current_index].1.text(), "Second match");
|
||
|
||
// n - ещё один
|
||
current_index = (current_index + 1) % results.len();
|
||
assert_eq!(current_index, 2);
|
||
assert_eq!(results[current_index].1.text(), "Third match");
|
||
|
||
// n - wrap around к первому
|
||
current_index = (current_index + 1) % results.len();
|
||
assert_eq!(current_index, 0);
|
||
assert_eq!(results[current_index].1.text(), "First match");
|
||
|
||
// N - предыдущий (wrap to last)
|
||
current_index = if current_index == 0 {
|
||
results.len() - 1
|
||
} else {
|
||
current_index - 1
|
||
};
|
||
assert_eq!(current_index, 2);
|
||
assert_eq!(results[current_index].1.text(), "Third match");
|
||
}
|
||
|
||
/// Test: Поиск с учётом регистра (case-insensitive)
|
||
#[test]
|
||
fn test_search_case_insensitive() {
|
||
let mut client = FakeTdClient::new();
|
||
|
||
let msg1 = TestMessageBuilder::new("HELLO", 100).build();
|
||
let msg2 = TestMessageBuilder::new("hello", 101).build();
|
||
let msg3 = TestMessageBuilder::new("HeLLo", 102).build();
|
||
|
||
client = client.with_messages(123, vec![msg1, msg2, msg3]);
|
||
|
||
// Ищем "hello" (lowercase)
|
||
let query = "hello".to_lowercase();
|
||
let messages = client.get_messages(123);
|
||
let found: Vec<_> = messages
|
||
.iter()
|
||
.filter(|m| m.text().to_lowercase().contains(&query))
|
||
.collect();
|
||
|
||
// Все 3 варианта должны найтись
|
||
assert_eq!(found.len(), 3);
|
||
}
|
||
|
||
/// Test: Поиск не находит ничего
|
||
#[test]
|
||
fn test_search_no_results() {
|
||
let mut client = FakeTdClient::new();
|
||
|
||
let msg1 = TestMessageBuilder::new("Hello", 100).build();
|
||
let msg2 = TestMessageBuilder::new("World", 101).build();
|
||
|
||
client = client.with_messages(123, vec![msg1, msg2]);
|
||
|
||
// Ищем "xyz" - не должно найтись
|
||
let query = "xyz".to_lowercase();
|
||
let messages = client.get_messages(123);
|
||
let found: Vec<_> = messages
|
||
.iter()
|
||
.filter(|m| m.text().to_lowercase().contains(&query))
|
||
.collect();
|
||
|
||
assert_eq!(found.len(), 0);
|
||
}
|
||
|
||
/// Test: Отмена поиска (Esc) восстанавливает обычный режим
|
||
#[test]
|
||
fn test_cancel_search_restores_normal_mode() {
|
||
let mut client = FakeTdClient::new();
|
||
|
||
let chat1 = create_test_chat("Mom", 123);
|
||
let chat2 = create_test_chat("Boss", 456);
|
||
|
||
client = client.with_chats(vec![chat1, chat2]);
|
||
|
||
// Симулируем: пользователь начал поиск
|
||
let mut is_searching = true;
|
||
let mut search_query = "mom".to_string();
|
||
|
||
// Фильтруем
|
||
let query = search_query.to_lowercase();
|
||
let filtered: Vec<_> = client
|
||
.get_chats()
|
||
.iter()
|
||
.filter(|c| c.title.to_lowercase().contains(&query))
|
||
.collect();
|
||
|
||
assert_eq!(filtered.len(), 1);
|
||
|
||
// Пользователь нажал Esc
|
||
is_searching = false;
|
||
search_query.clear();
|
||
|
||
// После отмены видим все чаты
|
||
let all_chats = client.get_chats();
|
||
assert_eq!(all_chats.len(), 2);
|
||
assert!(!is_searching);
|
||
assert_eq!(search_query, "");
|
||
}
|