style: auto-format entire codebase with cargo fmt (stable rustfmt.toml)
Some checks failed
ci/woodpecker/pr/check Pipeline 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

This commit is contained in:
Mikhail Kilin
2026-02-22 17:09:51 +03:00
parent 2442a90e23
commit 264f183510
90 changed files with 1632 additions and 1450 deletions

View File

@@ -17,11 +17,7 @@ fn test_open_account_switcher() {
assert!(app.account_switcher.is_some());
match &app.account_switcher {
Some(AccountSwitcherState::SelectAccount {
accounts,
selected_index,
current_account,
}) => {
Some(AccountSwitcherState::SelectAccount { accounts, selected_index, current_account }) => {
assert!(!accounts.is_empty());
assert_eq!(*selected_index, 0);
assert_eq!(current_account, "default");
@@ -58,11 +54,7 @@ fn test_account_switcher_navigate_down() {
}
match &app.account_switcher {
Some(AccountSwitcherState::SelectAccount {
selected_index,
accounts,
..
}) => {
Some(AccountSwitcherState::SelectAccount { selected_index, accounts, .. }) => {
// Should be at the "Add account" item (index == accounts.len())
assert_eq!(*selected_index, accounts.len());
}
@@ -137,11 +129,7 @@ fn test_confirm_add_account_transitions_to_add_state() {
app.account_switcher_confirm();
match &app.account_switcher {
Some(AccountSwitcherState::AddAccount {
name_input,
cursor_position,
error,
}) => {
Some(AccountSwitcherState::AddAccount { name_input, cursor_position, error }) => {
assert!(name_input.is_empty());
assert_eq!(*cursor_position, 0);
assert!(error.is_none());

View File

@@ -1,8 +1,6 @@
// Integration tests for accounts module
use tele_tui::accounts::{
account_db_path, validate_account_name, AccountProfile, AccountsConfig,
};
use tele_tui::accounts::{account_db_path, validate_account_name, AccountProfile, AccountsConfig};
#[test]
fn test_default_single_config() {

View File

@@ -65,16 +65,14 @@ fn test_incoming_message_shows_unread_badge() {
.last_message("Как дела?")
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.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)");
@@ -87,9 +85,13 @@ fn test_incoming_message_shows_unread_badge() {
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);
assert!(
output_after.contains("(1)"),
"After: should contain (1)\nActual output:\n{}",
output_after
);
}
#[tokio::test]
@@ -127,39 +129,44 @@ async fn test_opening_chat_clears_unread_badge() {
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);
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
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;
@@ -169,9 +176,13 @@ async fn test_opening_chat_clears_unread_badge() {
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);
assert!(
!output_after.contains("(3)"),
"After opening: should not contain (3)\nActual output:\n{}",
output_after
);
}
#[tokio::test]
@@ -202,7 +213,7 @@ async fn test_opening_chat_loads_many_messages() {
// Открываем чат - загружаем историю (запрашиваем 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(),
@@ -244,7 +255,7 @@ async fn test_chat_history_chunked_loading() {
// Тест 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,
@@ -254,13 +265,13 @@ async fn test_chat_history_chunked_loading() {
// Проверяем что сообщения в правильном порядке (от старых к новым)
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[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,
@@ -273,7 +284,7 @@ async fn test_chat_history_chunked_loading() {
// Тест 3: Запрашиваем 200 сообщений, но есть только 120
let limited_messages = app.td_client.get_chat_history(chat_id, 200).await.unwrap();
assert_eq!(
limited_messages.len(),
120,
@@ -307,8 +318,12 @@ async fn test_chat_history_loads_all_without_limit() {
// Загружаем без лимита (i32::MAX)
let chat_id = ChatId::new(1001);
let all = app.td_client.get_chat_history(chat_id, i32::MAX).await.unwrap();
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");
@@ -338,25 +353,29 @@ async fn test_load_older_messages_pagination() {
.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();
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");
@@ -473,7 +492,7 @@ fn snapshot_chat_search_mode() {
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();
@@ -493,4 +512,3 @@ fn snapshot_chat_with_online_status() {
let output = buffer_to_string(&buffer);
assert_snapshot!("chat_with_online_status", output);
}

View File

@@ -1,6 +1,9 @@
// Integration tests for config flow
use tele_tui::config::{AudioConfig, Config, ColorsConfig, GeneralConfig, ImagesConfig, Keybindings, NotificationsConfig};
use tele_tui::config::{
AudioConfig, ColorsConfig, Config, GeneralConfig, ImagesConfig, Keybindings,
NotificationsConfig,
};
/// Test: Дефолтные значения конфигурации
#[test]
@@ -22,9 +25,7 @@ fn test_config_default_values() {
#[test]
fn test_config_custom_values() {
let config = Config {
general: GeneralConfig {
timezone: "+05:00".to_string(),
},
general: GeneralConfig { timezone: "+05:00".to_string() },
colors: ColorsConfig {
incoming_message: "cyan".to_string(),
outgoing_message: "blue".to_string(),
@@ -108,9 +109,7 @@ fn test_parse_color_case_insensitive() {
#[test]
fn test_config_toml_serialization() {
let original_config = Config {
general: GeneralConfig {
timezone: "-05:00".to_string(),
},
general: GeneralConfig { timezone: "-05:00".to_string() },
colors: ColorsConfig {
incoming_message: "cyan".to_string(),
outgoing_message: "blue".to_string(),
@@ -164,25 +163,19 @@ mod timezone_tests {
#[test]
fn test_timezone_formats() {
let positive = Config {
general: GeneralConfig {
timezone: "+03:00".to_string(),
},
general: GeneralConfig { timezone: "+03:00".to_string() },
..Default::default()
};
assert_eq!(positive.general.timezone, "+03:00");
let negative = Config {
general: GeneralConfig {
timezone: "-05:00".to_string(),
},
general: GeneralConfig { timezone: "-05:00".to_string() },
..Default::default()
};
assert_eq!(negative.general.timezone, "-05:00");
let zero = Config {
general: GeneralConfig {
timezone: "+00:00".to_string(),
},
general: GeneralConfig { timezone: "+00:00".to_string() },
..Default::default()
};
assert_eq!(zero.general.timezone, "+00:00");

View File

@@ -12,13 +12,19 @@ async fn test_delete_message_removes_from_list() {
let client = FakeTdClient::new();
// Отправляем сообщение
let msg = client.send_message(ChatId::new(123), "Delete me".to_string(), None, None).await.unwrap();
let msg = client
.send_message(ChatId::new(123), "Delete me".to_string(), None, None)
.await
.unwrap();
// Проверяем что сообщение есть
assert_eq!(client.get_messages(123).len(), 1);
// Удаляем сообщение
client.delete_messages(ChatId::new(123), vec![msg.id()], false).await.unwrap();
client
.delete_messages(ChatId::new(123), vec![msg.id()], false)
.await
.unwrap();
// Проверяем что удаление записалось
assert_eq!(client.get_deleted_messages().len(), 1);
@@ -34,15 +40,30 @@ async fn test_delete_multiple_messages() {
let client = FakeTdClient::new();
// Отправляем 3 сообщения
let msg1 = client.send_message(ChatId::new(123), "Message 1".to_string(), None, None).await.unwrap();
let msg2 = client.send_message(ChatId::new(123), "Message 2".to_string(), None, None).await.unwrap();
let msg3 = client.send_message(ChatId::new(123), "Message 3".to_string(), None, None).await.unwrap();
let msg1 = client
.send_message(ChatId::new(123), "Message 1".to_string(), None, None)
.await
.unwrap();
let msg2 = client
.send_message(ChatId::new(123), "Message 2".to_string(), None, None)
.await
.unwrap();
let msg3 = client
.send_message(ChatId::new(123), "Message 3".to_string(), None, None)
.await
.unwrap();
assert_eq!(client.get_messages(123).len(), 3);
// Удаляем первое и третье
client.delete_messages(ChatId::new(123), vec![msg1.id()], false).await.unwrap();
client.delete_messages(ChatId::new(123), vec![msg3.id()], false).await.unwrap();
client
.delete_messages(ChatId::new(123), vec![msg1.id()], false)
.await
.unwrap();
client
.delete_messages(ChatId::new(123), vec![msg3.id()], false)
.await
.unwrap();
// Проверяем историю удалений
assert_eq!(client.get_deleted_messages().len(), 2);
@@ -89,12 +110,18 @@ async fn test_delete_nonexistent_message() {
let client = FakeTdClient::new();
// Отправляем одно сообщение
let msg = client.send_message(ChatId::new(123), "Exists".to_string(), None, None).await.unwrap();
let msg = client
.send_message(ChatId::new(123), "Exists".to_string(), None, None)
.await
.unwrap();
assert_eq!(client.get_messages(123).len(), 1);
// Пытаемся удалить несуществующее
client.delete_messages(ChatId::new(123), vec![MessageId::new(999)], false).await.unwrap();
client
.delete_messages(ChatId::new(123), vec![MessageId::new(999)], false)
.await
.unwrap();
// Удаление записалось в историю
assert_eq!(client.get_deleted_messages().len(), 1);
@@ -112,7 +139,10 @@ async fn test_delete_nonexistent_message() {
async fn test_delete_with_confirmation_flow() {
let client = FakeTdClient::new();
let msg = client.send_message(ChatId::new(123), "To delete".to_string(), None, None).await.unwrap();
let msg = client
.send_message(ChatId::new(123), "To delete".to_string(), None, None)
.await
.unwrap();
// Шаг 1: Пользователь нажал 'd' -> показывается модалка (в App)
// В FakeTdClient просто проверяем что сообщение ещё есть
@@ -120,7 +150,10 @@ async fn test_delete_with_confirmation_flow() {
assert_eq!(client.get_deleted_messages().len(), 0);
// Шаг 2: Пользователь подтвердил 'y' -> удаляем
client.delete_messages(ChatId::new(123), vec![msg.id()], false).await.unwrap();
client
.delete_messages(ChatId::new(123), vec![msg.id()], false)
.await
.unwrap();
// Проверяем что удалено
assert_eq!(client.get_messages(123).len(), 0);
@@ -132,7 +165,10 @@ async fn test_delete_with_confirmation_flow() {
async fn test_cancel_delete_keeps_message() {
let client = FakeTdClient::new();
let msg = client.send_message(ChatId::new(123), "Keep me".to_string(), None, None).await.unwrap();
let msg = client
.send_message(ChatId::new(123), "Keep me".to_string(), None, None)
.await
.unwrap();
// Шаг 1: Пользователь нажал 'd' -> показалась модалка
assert_eq!(client.get_messages(123).len(), 1);

View File

@@ -3,8 +3,8 @@
mod helpers;
use helpers::test_data::{create_test_chat, TestChatBuilder};
use tele_tui::types::{ChatId, MessageId};
use std::collections::HashMap;
use tele_tui::types::{ChatId, MessageId};
/// Простая структура для хранения черновиков (как в реальном App)
struct DraftManager {

View File

@@ -23,10 +23,7 @@ async fn test_user_journey_app_launch_to_chat_list() {
let chat2 = TestChatBuilder::new("Work Group", 102).build();
let chat3 = TestChatBuilder::new("Boss", 103).build();
let client = client
.with_chat(chat1)
.with_chat(chat2)
.with_chat(chat3);
let client = client.with_chat(chat1).with_chat(chat2).with_chat(chat3);
// 4. Симулируем загрузку чатов через load_chats
let loaded_chats = client.load_chats(50).await.unwrap();
@@ -58,9 +55,7 @@ async fn test_user_journey_open_chat_send_message() {
.outgoing()
.build();
let client = client
.with_message(123, msg1)
.with_message(123, msg2);
let client = client.with_message(123, msg1).with_message(123, msg2);
// 3. Открываем чат
client.open_chat(ChatId::new(123)).await.unwrap();
@@ -77,12 +72,10 @@ async fn test_user_journey_open_chat_send_message() {
assert_eq!(history[1].text(), "I'm good, thanks!");
// 7. Отправляем новое сообщение
let _new_msg = client.send_message(
ChatId::new(123),
"What's for dinner?".to_string(),
None,
None
).await.unwrap();
let _new_msg = client
.send_message(ChatId::new(123), "What's for dinner?".to_string(), None, None)
.await
.unwrap();
// 8. Проверяем что сообщение отправлено
assert_eq!(client.get_sent_messages().len(), 1);
@@ -153,34 +146,43 @@ async fn test_user_journey_multi_step_conversation() {
client.set_update_channel(tx);
// 4. Входящее сообщение от Alice
client.simulate_incoming_message(ChatId::new(789), "How's the project going?".to_string(), "Alice");
client.simulate_incoming_message(
ChatId::new(789),
"How's the project going?".to_string(),
"Alice",
);
// Проверяем update
let update = rx.try_recv().ok();
assert!(matches!(update, Some(TdUpdate::NewMessage { .. })));
// 5. Отвечаем
client.send_message(
ChatId::new(789),
"Almost done! Just need to finish tests.".to_string(),
None,
None
).await.unwrap();
client
.send_message(
ChatId::new(789),
"Almost done! Just need to finish tests.".to_string(),
None,
None,
)
.await
.unwrap();
// 6. Проверяем историю после первого обмена
let history1 = client.get_chat_history(ChatId::new(789), 50).await.unwrap();
assert_eq!(history1.len(), 2);
// 7. Еще одно входящее сообщение
client.simulate_incoming_message(ChatId::new(789), "Great! Let me know if you need help.".to_string(), "Alice");
client.simulate_incoming_message(
ChatId::new(789),
"Great! Let me know if you need help.".to_string(),
"Alice",
);
// 8. Снова отвечаем
client.send_message(
ChatId::new(789),
"Will do, thanks!".to_string(),
None,
None
).await.unwrap();
client
.send_message(ChatId::new(789), "Will do, thanks!".to_string(), None, None)
.await
.unwrap();
// 9. Финальная проверка истории
let final_history = client.get_chat_history(ChatId::new(789), 50).await.unwrap();
@@ -219,24 +221,20 @@ async fn test_user_journey_switch_chats() {
assert_eq!(client.get_current_chat_id(), Some(111));
// 3. Отправляем сообщение в первом чате
client.send_message(
ChatId::new(111),
"Message in chat 1".to_string(),
None,
None
).await.unwrap();
client
.send_message(ChatId::new(111), "Message in chat 1".to_string(), None, None)
.await
.unwrap();
// 4. Переключаемся на второй чат
client.open_chat(ChatId::new(222)).await.unwrap();
assert_eq!(client.get_current_chat_id(), Some(222));
// 5. Отправляем сообщение во втором чате
client.send_message(
ChatId::new(222),
"Message in chat 2".to_string(),
None,
None
).await.unwrap();
client
.send_message(ChatId::new(222), "Message in chat 2".to_string(), None, None)
.await
.unwrap();
// 6. Переключаемся на третий чат
client.open_chat(ChatId::new(333)).await.unwrap();
@@ -270,12 +268,10 @@ async fn test_user_journey_edit_during_conversation() {
client.open_chat(ChatId::new(555)).await.unwrap();
// 2. Отправляем сообщение с опечаткой
let msg = client.send_message(
ChatId::new(555),
"I'll be there at 5pm tomorow".to_string(),
None,
None
).await.unwrap();
let msg = client
.send_message(ChatId::new(555), "I'll be there at 5pm tomorow".to_string(), None, None)
.await
.unwrap();
// 3. Проверяем что сообщение отправлено
let history = client.get_chat_history(ChatId::new(555), 50).await.unwrap();
@@ -283,17 +279,19 @@ async fn test_user_journey_edit_during_conversation() {
assert_eq!(history[0].text(), "I'll be there at 5pm tomorow");
// 4. Исправляем опечатку
client.edit_message(
ChatId::new(555),
msg.id(),
"I'll be there at 5pm tomorrow".to_string()
).await.unwrap();
client
.edit_message(ChatId::new(555), msg.id(), "I'll be there at 5pm tomorrow".to_string())
.await
.unwrap();
// 5. Проверяем что сообщение отредактировано
let edited_history = client.get_chat_history(ChatId::new(555), 50).await.unwrap();
assert_eq!(edited_history.len(), 1);
assert_eq!(edited_history[0].text(), "I'll be there at 5pm tomorrow");
assert!(edited_history[0].metadata.edit_date > 0, "Должна быть установлена дата редактирования");
assert!(
edited_history[0].metadata.edit_date > 0,
"Должна быть установлена дата редактирования"
);
// 6. Проверяем историю редактирований
assert_eq!(client.get_edited_messages().len(), 1);
@@ -315,7 +313,11 @@ async fn test_user_journey_reply_in_conversation() {
client.set_update_channel(tx);
// 3. Входящее сообщение с вопросом
client.simulate_incoming_message(ChatId::new(666), "Can you send me the report?".to_string(), "Charlie");
client.simulate_incoming_message(
ChatId::new(666),
"Can you send me the report?".to_string(),
"Charlie",
);
let update = rx.try_recv().ok();
assert!(matches!(update, Some(TdUpdate::NewMessage { .. })));
@@ -324,12 +326,10 @@ async fn test_user_journey_reply_in_conversation() {
let question_msg_id = history[0].id();
// 4. Отправляем другое сообщение (не связанное)
client.send_message(
ChatId::new(666),
"Working on it now".to_string(),
None,
None
).await.unwrap();
client
.send_message(ChatId::new(666), "Working on it now".to_string(), None, None)
.await
.unwrap();
// 5. Отвечаем на конкретный вопрос (reply)
let reply_info = Some(tele_tui::tdlib::ReplyInfo {
@@ -338,12 +338,15 @@ async fn test_user_journey_reply_in_conversation() {
text: "Can you send me the report?".to_string(),
});
client.send_message(
ChatId::new(666),
"Sure, sending now!".to_string(),
Some(question_msg_id),
reply_info
).await.unwrap();
client
.send_message(
ChatId::new(666),
"Sure, sending now!".to_string(),
Some(question_msg_id),
reply_info,
)
.await
.unwrap();
// 6. Проверяем что reply сохранён
let final_history = client.get_chat_history(ChatId::new(666), 50).await.unwrap();
@@ -376,12 +379,10 @@ async fn test_user_journey_network_state_changes() {
// 4. Открываем чат и отправляем сообщение
client.open_chat(ChatId::new(888)).await.unwrap();
client.send_message(
ChatId::new(888),
"Test message".to_string(),
None,
None
).await.unwrap();
client
.send_message(ChatId::new(888), "Test message".to_string(), None, None)
.await
.unwrap();
// Очищаем канал от update NewMessage
let _ = rx.try_recv();
@@ -391,8 +392,14 @@ async fn test_user_journey_network_state_changes() {
// Проверяем update
let update = rx.try_recv().ok();
assert!(matches!(update, Some(TdUpdate::ConnectionState { state: NetworkState::WaitingForNetwork })),
"Expected ConnectionState update, got: {:?}", update);
assert!(
matches!(
update,
Some(TdUpdate::ConnectionState { state: NetworkState::WaitingForNetwork })
),
"Expected ConnectionState update, got: {:?}",
update
);
// 6. Проверяем что состояние изменилось
assert_eq!(client.get_network_state(), NetworkState::WaitingForNetwork);
@@ -405,12 +412,10 @@ async fn test_user_journey_network_state_changes() {
assert_eq!(client.get_network_state(), NetworkState::Ready);
// 8. Отправляем сообщение после восстановления
client.send_message(
ChatId::new(888),
"Connection restored!".to_string(),
None,
None
).await.unwrap();
client
.send_message(ChatId::new(888), "Connection restored!".to_string(), None, None)
.await
.unwrap();
// 9. Проверяем что оба сообщения в истории
let history = client.get_chat_history(ChatId::new(888), 50).await.unwrap();

View File

@@ -12,10 +12,16 @@ async fn test_edit_message_changes_text() {
let client = FakeTdClient::new();
// Отправляем сообщение
let msg = client.send_message(ChatId::new(123), "Original text".to_string(), None, None).await.unwrap();
let msg = client
.send_message(ChatId::new(123), "Original text".to_string(), None, None)
.await
.unwrap();
// Редактируем сообщение
client.edit_message(ChatId::new(123), msg.id(), "Edited text".to_string()).await.unwrap();
client
.edit_message(ChatId::new(123), msg.id(), "Edited text".to_string())
.await
.unwrap();
// Проверяем что редактирование записалось
assert_eq!(client.get_edited_messages().len(), 1);
@@ -34,7 +40,10 @@ async fn test_edit_message_sets_edit_date() {
let client = FakeTdClient::new();
// Отправляем сообщение
let msg = client.send_message(ChatId::new(123), "Original".to_string(), None, None).await.unwrap();
let msg = client
.send_message(ChatId::new(123), "Original".to_string(), None, None)
.await
.unwrap();
// Получаем дату до редактирования
let messages_before = client.get_messages(123);
@@ -42,7 +51,10 @@ async fn test_edit_message_sets_edit_date() {
assert_eq!(messages_before[0].metadata.edit_date, 0); // Не редактировалось
// Редактируем сообщение
client.edit_message(ChatId::new(123), msg.id(), "Edited".to_string()).await.unwrap();
client
.edit_message(ChatId::new(123), msg.id(), "Edited".to_string())
.await
.unwrap();
// Проверяем что edit_date установлена
let messages_after = client.get_messages(123);
@@ -78,16 +90,28 @@ async fn test_can_only_edit_own_messages() {
async fn test_multiple_edits_of_same_message() {
let client = FakeTdClient::new();
let msg = client.send_message(ChatId::new(123), "Version 1".to_string(), None, None).await.unwrap();
let msg = client
.send_message(ChatId::new(123), "Version 1".to_string(), None, None)
.await
.unwrap();
// Первое редактирование
client.edit_message(ChatId::new(123), msg.id(), "Version 2".to_string()).await.unwrap();
client
.edit_message(ChatId::new(123), msg.id(), "Version 2".to_string())
.await
.unwrap();
// Второе редактирование
client.edit_message(ChatId::new(123), msg.id(), "Version 3".to_string()).await.unwrap();
client
.edit_message(ChatId::new(123), msg.id(), "Version 3".to_string())
.await
.unwrap();
// Третье редактирование
client.edit_message(ChatId::new(123), msg.id(), "Final version".to_string()).await.unwrap();
client
.edit_message(ChatId::new(123), msg.id(), "Final version".to_string())
.await
.unwrap();
// Проверяем что все 3 редактирования записаны
assert_eq!(client.get_edited_messages().len(), 3);
@@ -107,7 +131,9 @@ async fn test_edit_nonexistent_message() {
let client = FakeTdClient::new();
// Пытаемся отредактировать несуществующее сообщение
let result = client.edit_message(ChatId::new(123), MessageId::new(999), "New text".to_string()).await;
let result = client
.edit_message(ChatId::new(123), MessageId::new(999), "New text".to_string())
.await;
// Должна вернуться ошибка
assert!(result.is_err());
@@ -124,7 +150,10 @@ async fn test_edit_nonexistent_message() {
async fn test_edit_history_tracking() {
let client = FakeTdClient::new();
let msg = client.send_message(ChatId::new(123), "Original".to_string(), None, None).await.unwrap();
let msg = client
.send_message(ChatId::new(123), "Original".to_string(), None, None)
.await
.unwrap();
// Симулируем начало редактирования -> изменение -> отмена
// Отменять на уровне FakeTdClient нельзя, но можно проверить что original сохранён
@@ -134,14 +163,20 @@ async fn test_edit_history_tracking() {
let original = messages_before[0].text().to_string();
// Редактируем
client.edit_message(ChatId::new(123), msg.id(), "Edited".to_string()).await.unwrap();
client
.edit_message(ChatId::new(123), msg.id(), "Edited".to_string())
.await
.unwrap();
// Проверяем что изменилось
let messages_edited = client.get_messages(123);
assert_eq!(messages_edited[0].text(), "Edited");
// Можем "отменить" редактирование вернув original
client.edit_message(ChatId::new(123), msg.id(), original).await.unwrap();
client
.edit_message(ChatId::new(123), msg.id(), original)
.await
.unwrap();
// Проверяем что вернулось
let messages_restored = client.get_messages(123);

View File

@@ -1,8 +1,8 @@
// Test App builder
use super::FakeTdClient;
use ratatui::widgets::ListState;
use std::collections::HashMap;
use super::FakeTdClient;
use tele_tui::app::{App, AppScreen, ChatState, InputMode};
use tele_tui::config::Config;
use tele_tui::tdlib::AuthState;
@@ -135,7 +135,8 @@ impl TestAppBuilder {
/// Подтверждение удаления
pub fn delete_confirmation(mut self, message_id: i64) -> Self {
self.chat_state = Some(ChatState::DeleteConfirmation { message_id: MessageId::new(message_id) });
self.chat_state =
Some(ChatState::DeleteConfirmation { message_id: MessageId::new(message_id) });
self
}
@@ -181,9 +182,7 @@ impl TestAppBuilder {
/// Режим пересылки сообщения
pub fn forward_mode(mut self, message_id: i64) -> Self {
self.chat_state = Some(ChatState::Forward {
message_id: MessageId::new(message_id),
});
self.chat_state = Some(ChatState::Forward { message_id: MessageId::new(message_id) });
self
}
@@ -224,17 +223,17 @@ impl TestAppBuilder {
pub fn build(self) -> App<FakeTdClient> {
// Создаём FakeTdClient с чатами и сообщениями
let mut fake_client = FakeTdClient::new();
// Добавляем чаты
for chat in &self.chats {
fake_client = fake_client.with_chat(chat.clone());
}
// Добавляем сообщения
for (chat_id, messages) in self.messages {
fake_client = fake_client.with_messages(chat_id, messages);
}
// Устанавливаем текущий чат если нужно
if let Some(chat_id) = self.selected_chat_id {
*fake_client.current_chat_id.lock().unwrap() = Some(chat_id);
@@ -244,7 +243,7 @@ impl TestAppBuilder {
if let Some(auth_state) = self.auth_state {
fake_client = fake_client.with_auth_state(auth_state);
}
// Создаём App с FakeTdClient
let mut app = App::with_client(self.config, fake_client);
@@ -254,7 +253,7 @@ impl TestAppBuilder {
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;

View File

@@ -2,22 +2,48 @@
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use tele_tui::tdlib::{AuthState, ChatInfo, MessageInfo, NetworkState, ProfileInfo, ReplyInfo};
use tele_tui::tdlib::types::{FolderInfo, ReactionInfo};
use tele_tui::tdlib::{AuthState, ChatInfo, MessageInfo, NetworkState, ProfileInfo, ReplyInfo};
use tele_tui::types::{ChatId, MessageId, UserId};
use tokio::sync::mpsc;
/// Update события от TDLib (упрощённая версия)
#[derive(Debug, Clone)]
pub enum TdUpdate {
NewMessage { chat_id: ChatId, message: MessageInfo },
MessageContent { chat_id: ChatId, message_id: MessageId, new_text: String },
DeleteMessages { chat_id: ChatId, message_ids: Vec<MessageId> },
ChatAction { chat_id: ChatId, user_id: UserId, action: String },
MessageInteractionInfo { chat_id: ChatId, message_id: MessageId, reactions: Vec<ReactionInfo> },
ConnectionState { state: NetworkState },
ChatReadOutbox { chat_id: ChatId, last_read_outbox_message_id: MessageId },
ChatDraftMessage { chat_id: ChatId, draft_text: Option<String> },
NewMessage {
chat_id: ChatId,
message: MessageInfo,
},
MessageContent {
chat_id: ChatId,
message_id: MessageId,
new_text: String,
},
DeleteMessages {
chat_id: ChatId,
message_ids: Vec<MessageId>,
},
ChatAction {
chat_id: ChatId,
user_id: UserId,
action: String,
},
MessageInteractionInfo {
chat_id: ChatId,
message_id: MessageId,
reactions: Vec<ReactionInfo>,
},
ConnectionState {
state: NetworkState,
},
ChatReadOutbox {
chat_id: ChatId,
last_read_outbox_message_id: MessageId,
},
ChatDraftMessage {
chat_id: ChatId,
draft_text: Option<String>,
},
}
/// Упрощённый mock TDLib клиента для тестов
@@ -30,14 +56,14 @@ pub struct FakeTdClient {
pub profiles: Arc<Mutex<HashMap<i64, ProfileInfo>>>,
pub drafts: Arc<Mutex<HashMap<i64, String>>>,
pub available_reactions: Arc<Mutex<Vec<String>>>,
// Состояние
pub network_state: Arc<Mutex<NetworkState>>,
pub typing_chat_id: Arc<Mutex<Option<i64>>>,
pub current_chat_id: Arc<Mutex<Option<i64>>>,
pub current_pinned_message: Arc<Mutex<Option<MessageInfo>>>,
pub auth_state: Arc<Mutex<AuthState>>,
// История действий (для проверки в тестах)
pub sent_messages: Arc<Mutex<Vec<SentMessage>>>,
pub edited_messages: Arc<Mutex<Vec<EditedMessage>>>,
@@ -45,12 +71,12 @@ pub struct FakeTdClient {
pub forwarded_messages: Arc<Mutex<Vec<ForwardedMessages>>>,
pub searched_queries: Arc<Mutex<Vec<SearchQuery>>>,
pub viewed_messages: Arc<Mutex<Vec<(i64, Vec<i64>)>>>, // (chat_id, message_ids)
pub chat_actions: Arc<Mutex<Vec<(i64, String)>>>, // (chat_id, action)
pub chat_actions: Arc<Mutex<Vec<(i64, String)>>>, // (chat_id, action)
pub pending_view_messages: Arc<Mutex<Vec<(ChatId, Vec<MessageId>)>>>, // Очередь для отметки как прочитанные
// Update channel для симуляции событий
pub update_tx: Arc<Mutex<Option<mpsc::UnboundedSender<TdUpdate>>>>,
// Скачанные файлы (file_id -> local_path)
pub downloaded_files: Arc<Mutex<HashMap<i32, String>>>,
@@ -142,8 +168,14 @@ impl FakeTdClient {
profiles: Arc::new(Mutex::new(HashMap::new())),
drafts: Arc::new(Mutex::new(HashMap::new())),
available_reactions: Arc::new(Mutex::new(vec![
"👍".to_string(), "❤️".to_string(), "😂".to_string(), "😮".to_string(),
"😢".to_string(), "🙏".to_string(), "👏".to_string(), "🔥".to_string(),
"👍".to_string(),
"❤️".to_string(),
"😂".to_string(),
"😮".to_string(),
"😢".to_string(),
"🙏".to_string(),
"👏".to_string(),
"🔥".to_string(),
])),
network_state: Arc::new(Mutex::new(NetworkState::Ready)),
typing_chat_id: Arc::new(Mutex::new(None)),
@@ -164,14 +196,14 @@ impl FakeTdClient {
fail_next_operation: Arc::new(Mutex::new(false)),
}
}
/// Создать update channel для получения событий
pub fn with_update_channel(self) -> (Self, mpsc::UnboundedReceiver<TdUpdate>) {
let (tx, rx) = mpsc::unbounded_channel();
*self.update_tx.lock().unwrap() = Some(tx);
(self, rx)
}
/// Включить симуляцию задержек (как в реальном TDLib)
pub fn with_delays(mut self) -> Self {
self.simulate_delays = true;
@@ -179,7 +211,7 @@ impl FakeTdClient {
}
// ==================== Builder Methods ====================
/// Добавить чат
pub fn with_chat(self, chat: ChatInfo) -> Self {
self.chats.lock().unwrap().push(chat);
@@ -205,16 +237,16 @@ impl FakeTdClient {
/// Добавить несколько сообщений в чат
pub fn with_messages(self, chat_id: i64, messages: Vec<MessageInfo>) -> Self {
self.messages
.lock()
.unwrap()
.insert(chat_id, messages);
self.messages.lock().unwrap().insert(chat_id, messages);
self
}
/// Добавить папку
pub fn with_folder(self, id: i32, name: &str) -> Self {
self.folders.lock().unwrap().push(FolderInfo { id, name: name.to_string() });
self.folders
.lock()
.unwrap()
.push(FolderInfo { id, name: name.to_string() });
self
}
@@ -241,10 +273,13 @@ impl FakeTdClient {
*self.auth_state.lock().unwrap() = state;
self
}
/// Добавить скачанный файл (для mock download_file)
pub fn with_downloaded_file(self, file_id: i32, path: &str) -> Self {
self.downloaded_files.lock().unwrap().insert(file_id, path.to_string());
self.downloaded_files
.lock()
.unwrap()
.insert(file_id, path.to_string());
self
}
@@ -255,60 +290,76 @@ impl FakeTdClient {
}
// ==================== Async TDLib Operations ====================
/// Загрузить список чатов
pub async fn load_chats(&self, limit: usize) -> Result<Vec<ChatInfo>, String> {
if self.should_fail() {
return Err("Failed to load chats".to_string());
}
if self.simulate_delays {
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
}
let chats = self.chats.lock().unwrap().iter().take(limit).cloned().collect();
let chats = self
.chats
.lock()
.unwrap()
.iter()
.take(limit)
.cloned()
.collect();
Ok(chats)
}
/// Открыть чат
pub async fn open_chat(&self, chat_id: ChatId) -> Result<(), String> {
if self.should_fail() {
return Err("Failed to open chat".to_string());
}
*self.current_chat_id.lock().unwrap() = Some(chat_id.as_i64());
Ok(())
}
/// Получить историю чата
pub async fn get_chat_history(&self, chat_id: ChatId, limit: i32) -> Result<Vec<MessageInfo>, String> {
pub async fn get_chat_history(
&self,
chat_id: ChatId,
limit: i32,
) -> Result<Vec<MessageInfo>, String> {
if self.should_fail() {
return Err("Failed to load history".to_string());
}
if self.simulate_delays {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
let messages = self.messages
let messages = self
.messages
.lock()
.unwrap()
.get(&chat_id.as_i64())
.map(|msgs| msgs.iter().take(limit as usize).cloned().collect())
.unwrap_or_default();
Ok(messages)
}
/// Загрузить старые сообщения
pub async fn load_older_messages(&self, chat_id: ChatId, from_message_id: MessageId) -> Result<Vec<MessageInfo>, String> {
pub async fn load_older_messages(
&self,
chat_id: ChatId,
from_message_id: MessageId,
) -> Result<Vec<MessageInfo>, String> {
if self.should_fail() {
return Err("Failed to load older messages".to_string());
}
let messages = self.messages.lock().unwrap();
let chat_messages = messages.get(&chat_id.as_i64()).ok_or("Chat not found")?;
// Найти индекс сообщения и вернуть предыдущие
if let Some(idx) = chat_messages.iter().position(|m| m.id() == from_message_id) {
let older: Vec<_> = chat_messages.iter().take(idx).cloned().collect();
@@ -329,24 +380,24 @@ impl FakeTdClient {
if self.should_fail() {
return Err("Failed to send message".to_string());
}
if self.simulate_delays {
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
}
let message_id = MessageId::new((self.sent_messages.lock().unwrap().len() as i64) + 1000);
self.sent_messages.lock().unwrap().push(SentMessage {
chat_id: chat_id.as_i64(),
text: text.clone(),
reply_to,
reply_info: reply_info.clone(),
});
let message = MessageInfo::new(
message_id,
"You".to_string(),
true, // is_outgoing
true, // is_outgoing
text.clone(),
vec![], // entities
chrono::Utc::now().timestamp() as i32,
@@ -356,10 +407,10 @@ impl FakeTdClient {
true, // can_be_deleted_only_for_self
true, // can_be_deleted_for_all_users
reply_info,
None, // forward_from
None, // forward_from
vec![], // reactions
);
// Добавляем в историю
self.messages
.lock()
@@ -367,16 +418,13 @@ impl FakeTdClient {
.entry(chat_id.as_i64())
.or_insert_with(Vec::new)
.push(message.clone());
// Отправляем Update::NewMessage
self.send_update(TdUpdate::NewMessage {
chat_id,
message: message.clone(),
});
self.send_update(TdUpdate::NewMessage { chat_id, message: message.clone() });
Ok(message)
}
/// Редактировать сообщение
pub async fn edit_message(
&self,
@@ -387,41 +435,37 @@ impl FakeTdClient {
if self.should_fail() {
return Err("Failed to edit message".to_string());
}
if self.simulate_delays {
tokio::time::sleep(tokio::time::Duration::from_millis(150)).await;
}
self.edited_messages.lock().unwrap().push(EditedMessage {
chat_id: chat_id.as_i64(),
message_id,
new_text: new_text.clone(),
});
// Обновляем сообщение
let mut messages = self.messages.lock().unwrap();
if let Some(chat_msgs) = messages.get_mut(&chat_id.as_i64()) {
if let Some(msg) = chat_msgs.iter_mut().find(|m| m.id() == message_id) {
msg.content.text = new_text.clone();
msg.metadata.edit_date = msg.metadata.date + 60;
let updated = msg.clone();
drop(messages); // Освобождаем lock перед отправкой update
// Отправляем Update
self.send_update(TdUpdate::MessageContent {
chat_id,
message_id,
new_text,
});
self.send_update(TdUpdate::MessageContent { chat_id, message_id, new_text });
return Ok(updated);
}
}
Err("Message not found".to_string())
}
/// Удалить сообщения
pub async fn delete_messages(
&self,
@@ -432,33 +476,30 @@ impl FakeTdClient {
if self.should_fail() {
return Err("Failed to delete messages".to_string());
}
if self.simulate_delays {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
self.deleted_messages.lock().unwrap().push(DeletedMessages {
chat_id: chat_id.as_i64(),
message_ids: message_ids.clone(),
revoke,
});
// Удаляем из истории
let mut messages = self.messages.lock().unwrap();
if let Some(chat_msgs) = messages.get_mut(&chat_id.as_i64()) {
chat_msgs.retain(|m| !message_ids.contains(&m.id()));
}
drop(messages);
// Отправляем Update
self.send_update(TdUpdate::DeleteMessages {
chat_id,
message_ids,
});
self.send_update(TdUpdate::DeleteMessages { chat_id, message_ids });
Ok(())
}
/// Переслать сообщения
pub async fn forward_messages(
&self,
@@ -469,26 +510,33 @@ impl FakeTdClient {
if self.should_fail() {
return Err("Failed to forward messages".to_string());
}
if self.simulate_delays {
tokio::time::sleep(tokio::time::Duration::from_millis(150)).await;
}
self.forwarded_messages.lock().unwrap().push(ForwardedMessages {
from_chat_id: from_chat_id.as_i64(),
to_chat_id: to_chat_id.as_i64(),
message_ids,
});
self.forwarded_messages
.lock()
.unwrap()
.push(ForwardedMessages {
from_chat_id: from_chat_id.as_i64(),
to_chat_id: to_chat_id.as_i64(),
message_ids,
});
Ok(())
}
/// Поиск сообщений в чате
pub async fn search_messages(&self, chat_id: ChatId, query: &str) -> Result<Vec<MessageInfo>, String> {
pub async fn search_messages(
&self,
chat_id: ChatId,
query: &str,
) -> Result<Vec<MessageInfo>, String> {
if self.should_fail() {
return Err("Failed to search messages".to_string());
}
let messages = self.messages.lock().unwrap();
let results: Vec<_> = messages
.get(&chat_id.as_i64())
@@ -499,43 +547,49 @@ impl FakeTdClient {
.collect()
})
.unwrap_or_default();
self.searched_queries.lock().unwrap().push(SearchQuery {
chat_id: chat_id.as_i64(),
query: query.to_string(),
results_count: results.len(),
});
Ok(results)
}
/// Установить черновик
pub async fn set_draft_message(&self, chat_id: ChatId, text: String) -> Result<(), String> {
if text.is_empty() {
self.drafts.lock().unwrap().remove(&chat_id.as_i64());
} else {
self.drafts.lock().unwrap().insert(chat_id.as_i64(), text.clone());
self.drafts
.lock()
.unwrap()
.insert(chat_id.as_i64(), text.clone());
}
self.send_update(TdUpdate::ChatDraftMessage {
chat_id,
draft_text: if text.is_empty() { None } else { Some(text) },
});
Ok(())
}
/// Отправить действие в чате (typing, etc.)
pub async fn send_chat_action(&self, chat_id: ChatId, action: String) {
self.chat_actions.lock().unwrap().push((chat_id.as_i64(), action.clone()));
self.chat_actions
.lock()
.unwrap()
.push((chat_id.as_i64(), action.clone()));
if action == "Typing" {
*self.typing_chat_id.lock().unwrap() = Some(chat_id.as_i64());
} else if action == "Cancel" {
*self.typing_chat_id.lock().unwrap() = None;
}
}
/// Получить доступные реакции для сообщения
pub async fn get_message_available_reactions(
&self,
@@ -545,10 +599,10 @@ impl FakeTdClient {
if self.should_fail() {
return Err("Failed to get available reactions".to_string());
}
Ok(self.available_reactions.lock().unwrap().clone())
}
/// Установить/удалить реакцию
pub async fn toggle_reaction(
&self,
@@ -559,15 +613,18 @@ impl FakeTdClient {
if self.should_fail() {
return Err("Failed to toggle reaction".to_string());
}
// Обновляем реакции на сообщении
let mut messages = self.messages.lock().unwrap();
if let Some(chat_msgs) = messages.get_mut(&chat_id.as_i64()) {
if let Some(msg) = chat_msgs.iter_mut().find(|m| m.id() == message_id) {
let reactions = &mut msg.interactions.reactions;
// Toggle logic
if let Some(pos) = reactions.iter().position(|r| r.emoji == emoji && r.is_chosen) {
if let Some(pos) = reactions
.iter()
.position(|r| r.emoji == emoji && r.is_chosen)
{
// Удаляем свою реакцию
reactions.remove(pos);
} else if let Some(reaction) = reactions.iter_mut().find(|r| r.emoji == emoji) {
@@ -582,10 +639,10 @@ impl FakeTdClient {
is_chosen: true,
});
}
let updated_reactions = reactions.clone();
drop(messages);
// Отправляем Update
self.send_update(TdUpdate::MessageInteractionInfo {
chat_id,
@@ -594,10 +651,10 @@ impl FakeTdClient {
});
}
}
Ok(())
}
/// Скачать файл (mock)
pub async fn download_file(&self, file_id: i32) -> Result<String, String> {
if self.should_fail() {
@@ -617,7 +674,7 @@ impl FakeTdClient {
if self.should_fail() {
return Err("Failed to get profile info".to_string());
}
self.profiles
.lock()
.unwrap()
@@ -625,7 +682,7 @@ impl FakeTdClient {
.cloned()
.ok_or_else(|| "Profile not found".to_string())
}
/// Отметить сообщения как просмотренные
pub async fn view_messages(&self, chat_id: ChatId, message_ids: Vec<MessageId>) {
self.viewed_messages
@@ -633,25 +690,25 @@ impl FakeTdClient {
.unwrap()
.push((chat_id.as_i64(), message_ids.iter().map(|id| id.as_i64()).collect()));
}
/// Загрузить чаты папки
pub async fn load_folder_chats(&self, _folder_id: i32, _limit: usize) -> Result<(), String> {
if self.should_fail() {
return Err("Failed to load folder chats".to_string());
}
Ok(())
}
// ==================== Helper Methods ====================
/// Отправить update в канал (если он установлен)
fn send_update(&self, update: TdUpdate) {
if let Some(tx) = self.update_tx.lock().unwrap().as_ref() {
let _ = tx.send(update);
}
}
/// Проверить нужно ли симулировать ошибку
fn should_fail(&self) -> bool {
let mut fail = self.fail_next_operation.lock().unwrap();
@@ -662,16 +719,16 @@ impl FakeTdClient {
false
}
}
/// Симулировать ошибку в следующей операции
pub fn fail_next(&self) {
*self.fail_next_operation.lock().unwrap() = true;
}
/// Симулировать входящее сообщение
pub fn simulate_incoming_message(&self, chat_id: ChatId, text: String, sender_name: &str) {
let message_id = MessageId::new(9000 + chrono::Utc::now().timestamp());
let message = MessageInfo::new(
message_id,
sender_name.to_string(),
@@ -688,7 +745,7 @@ impl FakeTdClient {
None,
vec![],
);
// Добавляем в историю
self.messages
.lock()
@@ -696,26 +753,22 @@ impl FakeTdClient {
.entry(chat_id.as_i64())
.or_insert_with(Vec::new)
.push(message.clone());
// Отправляем Update
self.send_update(TdUpdate::NewMessage { chat_id, message });
}
/// Симулировать typing от собеседника
pub fn simulate_typing(&self, chat_id: ChatId, user_id: UserId) {
self.send_update(TdUpdate::ChatAction {
chat_id,
user_id,
action: "Typing".to_string(),
});
self.send_update(TdUpdate::ChatAction { chat_id, user_id, action: "Typing".to_string() });
}
/// Симулировать изменение состояния сети
pub fn simulate_network_change(&self, state: NetworkState) {
*self.network_state.lock().unwrap() = state.clone();
self.send_update(TdUpdate::ConnectionState { state });
}
/// Симулировать прочтение сообщений
pub fn simulate_read_outbox(&self, chat_id: ChatId, last_read_message_id: MessageId) {
self.send_update(TdUpdate::ChatReadOutbox {
@@ -723,9 +776,9 @@ impl FakeTdClient {
last_read_outbox_message_id: last_read_message_id,
});
}
// ==================== Getters for Test Assertions ====================
/// Получить все чаты
pub fn get_chats(&self) -> Vec<ChatInfo> {
self.chats.lock().unwrap().clone()
@@ -745,57 +798,57 @@ impl FakeTdClient {
.cloned()
.unwrap_or_default()
}
/// Получить отправленные сообщения
pub fn get_sent_messages(&self) -> Vec<SentMessage> {
self.sent_messages.lock().unwrap().clone()
}
/// Получить отредактированные сообщения
pub fn get_edited_messages(&self) -> Vec<EditedMessage> {
self.edited_messages.lock().unwrap().clone()
}
/// Получить удалённые сообщения
pub fn get_deleted_messages(&self) -> Vec<DeletedMessages> {
self.deleted_messages.lock().unwrap().clone()
}
/// Получить пересланные сообщения
pub fn get_forwarded_messages(&self) -> Vec<ForwardedMessages> {
self.forwarded_messages.lock().unwrap().clone()
}
/// Получить поисковые запросы
pub fn get_search_queries(&self) -> Vec<SearchQuery> {
self.searched_queries.lock().unwrap().clone()
}
/// Получить просмотренные сообщения
pub fn get_viewed_messages(&self) -> Vec<(i64, Vec<i64>)> {
self.viewed_messages.lock().unwrap().clone()
}
/// Получить действия в чатах
pub fn get_chat_actions(&self) -> Vec<(i64, String)> {
self.chat_actions.lock().unwrap().clone()
}
/// Получить текущее состояние сети
pub fn get_network_state(&self) -> NetworkState {
self.network_state.lock().unwrap().clone()
}
/// Получить ID текущего открытого чата
pub fn get_current_chat_id(&self) -> Option<i64> {
*self.current_chat_id.lock().unwrap()
}
/// Установить update channel для получения событий
pub fn set_update_channel(&self, tx: mpsc::UnboundedSender<TdUpdate>) {
*self.update_tx.lock().unwrap() = Some(tx);
}
/// Очистить всю историю действий
pub fn clear_all_history(&self) {
self.sent_messages.lock().unwrap().clear();
@@ -835,10 +888,12 @@ mod tests {
async fn test_send_message() {
let client = FakeTdClient::new();
let chat_id = ChatId::new(123);
let result = client.send_message(chat_id, "Hello".to_string(), None, None).await;
let result = client
.send_message(chat_id, "Hello".to_string(), None, None)
.await;
assert!(result.is_ok());
let sent = client.get_sent_messages();
assert_eq!(sent.len(), 1);
assert_eq!(sent[0].text, "Hello");
@@ -849,12 +904,17 @@ mod tests {
async fn test_edit_message() {
let client = FakeTdClient::new();
let chat_id = ChatId::new(123);
let msg = client.send_message(chat_id, "Hello".to_string(), None, None).await.unwrap();
let msg = client
.send_message(chat_id, "Hello".to_string(), None, None)
.await
.unwrap();
let msg_id = msg.id();
let _ = client.edit_message(chat_id, msg_id, "Hello World".to_string()).await;
let _ = client
.edit_message(chat_id, msg_id, "Hello World".to_string())
.await;
let edited = client.get_edited_messages();
assert_eq!(edited.len(), 1);
assert_eq!(client.get_messages(123)[0].text(), "Hello World");
@@ -865,25 +925,30 @@ mod tests {
async fn test_delete_message() {
let client = FakeTdClient::new();
let chat_id = ChatId::new(123);
let msg = client.send_message(chat_id, "Hello".to_string(), None, None).await.unwrap();
let msg = client
.send_message(chat_id, "Hello".to_string(), None, None)
.await
.unwrap();
let msg_id = msg.id();
let _ = client.delete_messages(chat_id, vec![msg_id], false).await;
let deleted = client.get_deleted_messages();
assert_eq!(deleted.len(), 1);
assert_eq!(client.get_messages(123).len(), 0);
}
#[tokio::test]
async fn test_update_channel() {
let (client, mut rx) = FakeTdClient::new().with_update_channel();
let chat_id = ChatId::new(123);
// Отправляем сообщение
let _ = client.send_message(chat_id, "Test".to_string(), None, None).await;
let _ = client
.send_message(chat_id, "Test".to_string(), None, None)
.await;
// Проверяем что получили Update
if let Some(update) = rx.recv().await {
match update {
@@ -896,39 +961,43 @@ mod tests {
panic!("No update received");
}
}
#[tokio::test]
async fn test_simulate_incoming_message() {
let (client, mut rx) = FakeTdClient::new().with_update_channel();
let chat_id = ChatId::new(123);
client.simulate_incoming_message(chat_id, "Hello from Bob".to_string(), "Bob");
// Проверяем Update
if let Some(TdUpdate::NewMessage { message, .. }) = rx.recv().await {
assert_eq!(message.text(), "Hello from Bob");
assert_eq!(message.sender_name(), "Bob");
assert!(!message.is_outgoing());
}
// Проверяем что сообщение добавилось
assert_eq!(client.get_messages(123).len(), 1);
}
#[tokio::test]
async fn test_fail_next_operation() {
let client = FakeTdClient::new();
let chat_id = ChatId::new(123);
// Устанавливаем флаг ошибки
client.fail_next();
// Следующая операция должна упасть
let result = client.send_message(chat_id, "Test".to_string(), None, None).await;
let result = client
.send_message(chat_id, "Test".to_string(), None, None)
.await;
assert!(result.is_err());
// Но следующая должна пройти
let result2 = client.send_message(chat_id, "Test2".to_string(), None, None).await;
let result2 = client
.send_message(chat_id, "Test2".to_string(), None, None)
.await;
assert!(result2.is_ok());
}
}

View File

@@ -4,8 +4,11 @@ use super::fake_tdclient::FakeTdClient;
use async_trait::async_trait;
use std::path::PathBuf;
use tdlib_rs::enums::{ChatAction, Update};
use tele_tui::tdlib::{AuthState, ChatInfo, FolderInfo, MessageInfo, ProfileInfo, ReplyInfo, UserCache, UserOnlineStatus};
use tele_tui::tdlib::TdClientTrait;
use tele_tui::tdlib::{
AuthState, ChatInfo, FolderInfo, MessageInfo, ProfileInfo, ReplyInfo, UserCache,
UserOnlineStatus,
};
use tele_tui::types::{ChatId, MessageId, UserId};
#[async_trait]
@@ -55,11 +58,19 @@ impl TdClientTrait for FakeTdClient {
}
// ============ Message methods ============
async fn get_chat_history(&mut self, chat_id: ChatId, limit: i32) -> Result<Vec<MessageInfo>, String> {
async fn get_chat_history(
&mut self,
chat_id: ChatId,
limit: i32,
) -> Result<Vec<MessageInfo>, String> {
FakeTdClient::get_chat_history(self, chat_id, limit).await
}
async fn load_older_messages(&mut self, chat_id: ChatId, from_message_id: MessageId) -> Result<Vec<MessageInfo>, String> {
async fn load_older_messages(
&mut self,
chat_id: ChatId,
from_message_id: MessageId,
) -> Result<Vec<MessageInfo>, String> {
FakeTdClient::load_older_messages(self, chat_id, from_message_id).await
}
@@ -72,7 +83,11 @@ impl TdClientTrait for FakeTdClient {
// Not implemented for fake
}
async fn search_messages(&self, chat_id: ChatId, query: &str) -> Result<Vec<MessageInfo>, String> {
async fn search_messages(
&self,
chat_id: ChatId,
query: &str,
) -> Result<Vec<MessageInfo>, String> {
FakeTdClient::search_messages(self, chat_id, query).await
}
@@ -130,7 +145,10 @@ impl TdClientTrait for FakeTdClient {
let mut pending = self.pending_view_messages.lock().unwrap();
for (chat_id, message_ids) in pending.drain(..) {
let ids: Vec<i64> = message_ids.iter().map(|id| id.as_i64()).collect();
self.viewed_messages.lock().unwrap().push((chat_id.as_i64(), ids));
self.viewed_messages
.lock()
.unwrap()
.push((chat_id.as_i64(), ids));
}
}
@@ -189,13 +207,17 @@ impl TdClientTrait for FakeTdClient {
static AUTH_STATE_WAIT_PHONE: OnceLock<AuthState> = OnceLock::new();
static AUTH_STATE_WAIT_CODE: OnceLock<AuthState> = OnceLock::new();
static AUTH_STATE_WAIT_PASSWORD: OnceLock<AuthState> = OnceLock::new();
let current = self.auth_state.lock().unwrap();
match *current {
AuthState::Ready => &AUTH_STATE_READY,
AuthState::WaitPhoneNumber => AUTH_STATE_WAIT_PHONE.get_or_init(|| AuthState::WaitPhoneNumber),
AuthState::WaitPhoneNumber => {
AUTH_STATE_WAIT_PHONE.get_or_init(|| AuthState::WaitPhoneNumber)
}
AuthState::WaitCode => AUTH_STATE_WAIT_CODE.get_or_init(|| AuthState::WaitCode),
AuthState::WaitPassword => AUTH_STATE_WAIT_PASSWORD.get_or_init(|| AuthState::WaitPassword),
AuthState::WaitPassword => {
AUTH_STATE_WAIT_PASSWORD.get_or_init(|| AuthState::WaitPassword)
}
_ => &AUTH_STATE_READY,
}
}

View File

@@ -1,7 +1,7 @@
// Test data builders and fixtures
use tele_tui::tdlib::{ChatInfo, MessageInfo, ProfileInfo, ReplyInfo};
use tele_tui::tdlib::types::{ForwardInfo, ReactionInfo};
use tele_tui::tdlib::{ChatInfo, MessageInfo, ProfileInfo, ReplyInfo};
use tele_tui::types::{ChatId, MessageId};
/// Builder для создания тестового чата
@@ -177,9 +177,7 @@ impl TestMessageBuilder {
}
pub fn forwarded_from(mut self, sender: &str) -> Self {
self.forward_from = Some(ForwardInfo {
sender_name: sender.to_string(),
});
self.forward_from = Some(ForwardInfo { sender_name: sender.to_string() });
self
}

View File

@@ -292,7 +292,9 @@ async fn test_normal_mode_auto_enters_message_selection() {
#[tokio::test]
async fn test_album_navigation_skips_grouped_messages() {
let messages = vec![
TestMessageBuilder::new("Before album", 1).sender("Alice").build(),
TestMessageBuilder::new("Before album", 1)
.sender("Alice")
.build(),
TestMessageBuilder::new("Photo 1", 2)
.sender("Alice")
.media_album_id(100)
@@ -305,7 +307,9 @@ async fn test_album_navigation_skips_grouped_messages() {
.sender("Alice")
.media_album_id(100)
.build(),
TestMessageBuilder::new("After album", 5).sender("Alice").build(),
TestMessageBuilder::new("After album", 5)
.sender("Alice")
.build(),
];
let mut app = TestAppBuilder::new()
@@ -347,7 +351,9 @@ async fn test_album_navigation_skips_grouped_messages() {
#[tokio::test]
async fn test_album_navigation_start_at_album_end() {
let messages = vec![
TestMessageBuilder::new("Regular", 1).sender("Alice").build(),
TestMessageBuilder::new("Regular", 1)
.sender("Alice")
.build(),
TestMessageBuilder::new("Album Photo 1", 2)
.sender("Alice")
.media_album_id(200)

View File

@@ -3,12 +3,12 @@
mod helpers;
use helpers::app_builder::TestAppBuilder;
use tele_tui::tdlib::TdClientTrait;
use helpers::snapshot_utils::{buffer_to_string, render_to_buffer};
use helpers::test_data::{
create_test_chat, create_test_profile, TestChatBuilder, TestMessageBuilder,
};
use insta::assert_snapshot;
use tele_tui::tdlib::TdClientTrait;
#[test]
fn snapshot_delete_confirmation_modal() {
@@ -35,7 +35,16 @@ fn snapshot_emoji_picker_default() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("React to this", 1).build();
let reactions = vec!["👍".to_string(), "👎".to_string(), "❤️".to_string(), "🔥".to_string(), "😊".to_string(), "😢".to_string(), "😮".to_string(), "🎉".to_string()];
let reactions = vec![
"👍".to_string(),
"👎".to_string(),
"❤️".to_string(),
"🔥".to_string(),
"😊".to_string(),
"😢".to_string(),
"😮".to_string(),
"🎉".to_string(),
];
let mut app = TestAppBuilder::new()
.with_chat(chat)
@@ -57,7 +66,16 @@ fn snapshot_emoji_picker_with_selection() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("React to this", 1).build();
let reactions = vec!["👍".to_string(), "👎".to_string(), "❤️".to_string(), "🔥".to_string(), "😊".to_string(), "😢".to_string(), "😮".to_string(), "🎉".to_string()];
let reactions = vec![
"👍".to_string(),
"👎".to_string(),
"❤️".to_string(),
"🔥".to_string(),
"😊".to_string(),
"😢".to_string(),
"😮".to_string(),
"🎉".to_string(),
];
let mut app = TestAppBuilder::new()
.with_chat(chat)
@@ -160,7 +178,9 @@ fn snapshot_search_in_chat() {
.build();
// Устанавливаем результаты поиска
if let tele_tui::app::ChatState::SearchInChat { results, selected_index, .. } = &mut app.chat_state {
if let tele_tui::app::ChatState::SearchInChat { results, selected_index, .. } =
&mut app.chat_state
{
*results = vec![msg1, msg2];
*selected_index = 0;
}

View File

@@ -97,7 +97,9 @@ async fn test_typing_indicator_on() {
// Alice начала печатать в чате 123
// Симулируем через send_chat_action
client.send_chat_action(ChatId::new(123), "Typing".to_string()).await;
client
.send_chat_action(ChatId::new(123), "Typing".to_string())
.await;
assert_eq!(*client.typing_chat_id.lock().unwrap(), Some(123));
@@ -110,11 +112,15 @@ async fn test_typing_indicator_off() {
let client = FakeTdClient::new();
// Изначально Alice печатала
client.send_chat_action(ChatId::new(123), "Typing".to_string()).await;
client
.send_chat_action(ChatId::new(123), "Typing".to_string())
.await;
assert_eq!(*client.typing_chat_id.lock().unwrap(), Some(123));
// Alice перестала печатать
client.send_chat_action(ChatId::new(123), "Cancel".to_string()).await;
client
.send_chat_action(ChatId::new(123), "Cancel".to_string())
.await;
assert_eq!(*client.typing_chat_id.lock().unwrap(), None);

View File

@@ -12,10 +12,16 @@ async fn test_add_reaction_to_message() {
let client = FakeTdClient::new();
// Отправляем сообщение
let msg = client.send_message(ChatId::new(123), "React to this!".to_string(), None, None).await.unwrap();
let msg = client
.send_message(ChatId::new(123), "React to this!".to_string(), None, None)
.await
.unwrap();
// Добавляем реакцию
client.toggle_reaction(ChatId::new(123), msg.id(), "👍".to_string()).await.unwrap();
client
.toggle_reaction(ChatId::new(123), msg.id(), "👍".to_string())
.await
.unwrap();
// Проверяем что реакция записалась
let messages = client.get_messages(123);
@@ -46,7 +52,10 @@ async fn test_toggle_reaction_removes_it() {
let msg_id = messages_before[0].id();
// Toggle - удаляем свою реакцию
client.toggle_reaction(ChatId::new(123), msg_id, "👍".to_string()).await.unwrap();
client
.toggle_reaction(ChatId::new(123), msg_id, "👍".to_string())
.await
.unwrap();
let messages_after = client.get_messages(123);
assert_eq!(messages_after[0].reactions().len(), 0);
@@ -57,13 +66,28 @@ async fn test_toggle_reaction_removes_it() {
async fn test_multiple_reactions_on_one_message() {
let client = FakeTdClient::new();
let msg = client.send_message(ChatId::new(123), "Many reactions".to_string(), None, None).await.unwrap();
let msg = client
.send_message(ChatId::new(123), "Many reactions".to_string(), None, None)
.await
.unwrap();
// Добавляем несколько разных реакций
client.toggle_reaction(ChatId::new(123), msg.id(), "👍".to_string()).await.unwrap();
client.toggle_reaction(ChatId::new(123), msg.id(), "❤️".to_string()).await.unwrap();
client.toggle_reaction(ChatId::new(123), msg.id(), "😂".to_string()).await.unwrap();
client.toggle_reaction(ChatId::new(123), msg.id(), "🔥".to_string()).await.unwrap();
client
.toggle_reaction(ChatId::new(123), msg.id(), "👍".to_string())
.await
.unwrap();
client
.toggle_reaction(ChatId::new(123), msg.id(), "❤️".to_string())
.await
.unwrap();
client
.toggle_reaction(ChatId::new(123), msg.id(), "😂".to_string())
.await
.unwrap();
client
.toggle_reaction(ChatId::new(123), msg.id(), "🔥".to_string())
.await
.unwrap();
// Проверяем что все 4 реакции записались
let messages = client.get_messages(123);
@@ -151,7 +175,10 @@ async fn test_reaction_counter_increases() {
let msg_id = messages_before[0].id();
// Мы добавляем свою реакцию - счётчик должен увеличиться
client.toggle_reaction(ChatId::new(123), msg_id, "👍".to_string()).await.unwrap();
client
.toggle_reaction(ChatId::new(123), msg_id, "👍".to_string())
.await
.unwrap();
let messages = client.get_messages(123);
assert_eq!(messages[0].reactions()[0].count, 2);
@@ -177,7 +204,10 @@ async fn test_update_reaction_we_add_ours() {
let msg_id = messages_before[0].id();
// Добавляем нашу реакцию
client.toggle_reaction(ChatId::new(123), msg_id, "🔥".to_string()).await.unwrap();
client
.toggle_reaction(ChatId::new(123), msg_id, "🔥".to_string())
.await
.unwrap();
let messages = client.get_messages(123);
let reaction = &messages[0].reactions()[0];

View File

@@ -4,8 +4,8 @@ mod helpers;
use helpers::fake_tdclient::FakeTdClient;
use helpers::test_data::TestMessageBuilder;
use tele_tui::tdlib::ReplyInfo;
use tele_tui::tdlib::types::ForwardInfo;
use tele_tui::tdlib::ReplyInfo;
use tele_tui::types::{ChatId, MessageId};
/// Test: Reply создаёт сообщение с reply_to
@@ -28,7 +28,15 @@ async fn test_reply_creates_message_with_reply_to() {
};
// Отвечаем на него
let reply_msg = client.send_message(ChatId::new(123), "Answer!".to_string(), Some(MessageId::new(100)), Some(reply_info)).await.unwrap();
let reply_msg = client
.send_message(
ChatId::new(123),
"Answer!".to_string(),
Some(MessageId::new(100)),
Some(reply_info),
)
.await
.unwrap();
// Проверяем что ответ отправлен с reply_to
assert_eq!(client.get_sent_messages().len(), 1);
@@ -79,7 +87,10 @@ async fn test_cancel_reply_sends_without_reply_to() {
// Пользователь начал reply (r), потом отменил (Esc), затем отправил
// Это эмулируется отправкой без reply_to
client.send_message(ChatId::new(123), "Regular message".to_string(), None, None).await.unwrap();
client
.send_message(ChatId::new(123), "Regular message".to_string(), None, None)
.await
.unwrap();
// Проверяем что отправилось без reply_to
assert_eq!(client.get_sent_messages()[0].reply_to, None);
@@ -175,7 +186,15 @@ async fn test_reply_to_forwarded_message() {
};
// Отвечаем на пересланное сообщение
let reply_msg = client.send_message(ChatId::new(123), "Thanks for sharing!".to_string(), Some(MessageId::new(100)), Some(reply_info)).await.unwrap();
let reply_msg = client
.send_message(
ChatId::new(123),
"Thanks for sharing!".to_string(),
Some(MessageId::new(100)),
Some(reply_info),
)
.await
.unwrap();
// Проверяем что reply содержит reply_to
assert_eq!(client.get_sent_messages()[0].reply_to, Some(MessageId::new(100)));

View File

@@ -14,7 +14,10 @@ async fn test_send_text_message() {
let client = client.with_chat(chat);
// Отправляем сообщение
let msg = client.send_message(ChatId::new(123), "Hello, Mom!".to_string(), None, None).await.unwrap();
let msg = client
.send_message(ChatId::new(123), "Hello, Mom!".to_string(), None, None)
.await
.unwrap();
// Проверяем что сообщение было отправлено
assert_eq!(client.get_sent_messages().len(), 1);
@@ -36,13 +39,22 @@ async fn test_send_multiple_messages_updates_list() {
let client = FakeTdClient::new();
// Отправляем первое сообщение
let msg1 = client.send_message(ChatId::new(123), "Message 1".to_string(), None, None).await.unwrap();
let msg1 = client
.send_message(ChatId::new(123), "Message 1".to_string(), None, None)
.await
.unwrap();
// Отправляем второе сообщение
let msg2 = client.send_message(ChatId::new(123), "Message 2".to_string(), None, None).await.unwrap();
let msg2 = client
.send_message(ChatId::new(123), "Message 2".to_string(), None, None)
.await
.unwrap();
// Отправляем третье сообщение
let msg3 = client.send_message(ChatId::new(123), "Message 3".to_string(), None, None).await.unwrap();
let msg3 = client
.send_message(ChatId::new(123), "Message 3".to_string(), None, None)
.await
.unwrap();
// Проверяем что все 3 сообщения отслеживаются
assert_eq!(client.get_sent_messages().len(), 3);
@@ -66,7 +78,10 @@ async fn test_send_empty_message_technical() {
let client = FakeTdClient::new();
// FakeTdClient технически может отправить пустое сообщение
let msg = client.send_message(ChatId::new(123), "".to_string(), None, None).await.unwrap();
let msg = client
.send_message(ChatId::new(123), "".to_string(), None, None)
.await
.unwrap();
// Проверяем что оно отправилось (в реальном App это должно фильтроваться)
assert_eq!(client.get_sent_messages().len(), 1);
@@ -85,7 +100,10 @@ async fn test_send_message_with_markdown() {
let client = FakeTdClient::new();
let text = "**Bold** *italic* `code`";
client.send_message(ChatId::new(123), text.to_string(), None, None).await.unwrap();
client
.send_message(ChatId::new(123), text.to_string(), None, None)
.await
.unwrap();
// Проверяем что текст сохранился как есть (парсинг markdown - отдельная логика)
let messages = client.get_messages(123);
@@ -99,13 +117,22 @@ async fn test_send_messages_to_different_chats() {
let client = FakeTdClient::new();
// Отправляем в чат 123
client.send_message(ChatId::new(123), "Hello Mom".to_string(), None, None).await.unwrap();
client
.send_message(ChatId::new(123), "Hello Mom".to_string(), None, None)
.await
.unwrap();
// Отправляем в чат 456
client.send_message(ChatId::new(456), "Hello Boss".to_string(), None, None).await.unwrap();
client
.send_message(ChatId::new(456), "Hello Boss".to_string(), None, None)
.await
.unwrap();
// Отправляем ещё одно в чат 123
client.send_message(ChatId::new(123), "How are you?".to_string(), None, None).await.unwrap();
client
.send_message(ChatId::new(123), "How are you?".to_string(), None, None)
.await
.unwrap();
// Проверяем общее количество отправленных
assert_eq!(client.get_sent_messages().len(), 3);
@@ -128,7 +155,10 @@ async fn test_receive_incoming_message() {
let client = FakeTdClient::new();
// Добавляем существующее сообщение
client.send_message(ChatId::new(123), "My outgoing".to_string(), None, None).await.unwrap();
client
.send_message(ChatId::new(123), "My outgoing".to_string(), None, None)
.await
.unwrap();
// Симулируем входящее сообщение от собеседника
let incoming_msg = TestMessageBuilder::new("Hey there!", 2000)

View File

@@ -12,9 +12,9 @@ mod helpers;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use helpers::app_builder::TestAppBuilder;
use helpers::test_data::{create_test_chat, TestMessageBuilder};
use tele_tui::app::InputMode;
use tele_tui::app::methods::compose::ComposeMethods;
use tele_tui::app::methods::messages::MessageMethods;
use tele_tui::app::InputMode;
use tele_tui::input::handle_main_input;
fn key(code: KeyCode) -> KeyEvent {
@@ -32,9 +32,7 @@ fn ctrl_key(c: char) -> KeyEvent {
/// `i` в Normal mode → переход в Insert mode
#[tokio::test]
async fn test_i_enters_insert_mode() {
let messages = vec![
TestMessageBuilder::new("Hello", 1).outgoing().build(),
];
let messages = vec![TestMessageBuilder::new("Hello", 1).outgoing().build()];
let mut app = TestAppBuilder::new()
.with_chats(vec![create_test_chat("Chat", 101)])
.selected_chat(101)
@@ -54,9 +52,7 @@ async fn test_i_enters_insert_mode() {
/// `ш` (русская i) в Normal mode → переход в Insert mode
#[tokio::test]
async fn test_russian_i_enters_insert_mode() {
let messages = vec![
TestMessageBuilder::new("Hello", 1).outgoing().build(),
];
let messages = vec![TestMessageBuilder::new("Hello", 1).outgoing().build()];
let mut app = TestAppBuilder::new()
.with_chats(vec![create_test_chat("Chat", 101)])
.selected_chat(101)
@@ -72,9 +68,7 @@ async fn test_russian_i_enters_insert_mode() {
/// Esc в Insert mode → Normal mode + MessageSelection
#[tokio::test]
async fn test_esc_exits_insert_mode() {
let messages = vec![
TestMessageBuilder::new("Hello", 1).outgoing().build(),
];
let messages = vec![TestMessageBuilder::new("Hello", 1).outgoing().build()];
let mut app = TestAppBuilder::new()
.with_chats(vec![create_test_chat("Chat", 101)])
.selected_chat(101)
@@ -127,9 +121,9 @@ async fn test_close_chat_resets_input_mode() {
/// Auto-Insert при Reply (`r` в MessageSelection)
#[tokio::test]
async fn test_reply_auto_enters_insert_mode() {
let messages = vec![
TestMessageBuilder::new("Hello from friend", 1).sender("Friend").build(),
];
let messages = vec![TestMessageBuilder::new("Hello from friend", 1)
.sender("Friend")
.build()];
let mut app = TestAppBuilder::new()
.with_chats(vec![create_test_chat("Chat", 101)])
.selected_chat(101)
@@ -149,9 +143,7 @@ async fn test_reply_auto_enters_insert_mode() {
/// Auto-Insert при Edit (Enter в MessageSelection)
#[tokio::test]
async fn test_edit_auto_enters_insert_mode() {
let messages = vec![
TestMessageBuilder::new("My message", 1).outgoing().build(),
];
let messages = vec![TestMessageBuilder::new("My message", 1).outgoing().build()];
let mut app = TestAppBuilder::new()
.with_chats(vec![create_test_chat("Chat", 101)])
.selected_chat(101)
@@ -248,9 +240,7 @@ async fn test_k_types_in_insert_mode() {
/// `d` в Insert mode → набирает "d", НЕ удаляет сообщение
#[tokio::test]
async fn test_d_types_in_insert_mode() {
let messages = vec![
TestMessageBuilder::new("Hello", 1).outgoing().build(),
];
let messages = vec![TestMessageBuilder::new("Hello", 1).outgoing().build()];
let mut app = TestAppBuilder::new()
.with_chats(vec![create_test_chat("Chat", 101)])
.selected_chat(101)
@@ -268,9 +258,7 @@ async fn test_d_types_in_insert_mode() {
/// `r` в Insert mode → набирает "r", НЕ reply
#[tokio::test]
async fn test_r_types_in_insert_mode() {
let messages = vec![
TestMessageBuilder::new("Hello", 1).build(),
];
let messages = vec![TestMessageBuilder::new("Hello", 1).build()];
let mut app = TestAppBuilder::new()
.with_chats(vec![create_test_chat("Chat", 101)])
.selected_chat(101)
@@ -395,9 +383,7 @@ async fn test_k_navigates_in_normal_mode() {
/// `d` в Normal mode → показывает подтверждение удаления
#[tokio::test]
async fn test_d_deletes_in_normal_mode() {
let messages = vec![
TestMessageBuilder::new("My message", 1).outgoing().build(),
];
let messages = vec![TestMessageBuilder::new("My message", 1).outgoing().build()];
let mut app = TestAppBuilder::new()
.with_chats(vec![create_test_chat("Chat", 101)])
.selected_chat(101)
@@ -488,9 +474,7 @@ async fn test_ctrl_e_moves_to_end_in_insert() {
/// Esc из Insert при активном Reply → отменяет reply + Normal + MessageSelection
#[tokio::test]
async fn test_esc_from_insert_cancels_reply() {
let messages = vec![
TestMessageBuilder::new("Hello", 1).sender("Friend").build(),
];
let messages = vec![TestMessageBuilder::new("Hello", 1).sender("Friend").build()];
let mut app = TestAppBuilder::new()
.with_chats(vec![create_test_chat("Chat", 101)])
.selected_chat(101)
@@ -512,9 +496,7 @@ async fn test_esc_from_insert_cancels_reply() {
/// Esc из Insert при активном Editing → отменяет editing + Normal + MessageSelection
#[tokio::test]
async fn test_esc_from_insert_cancels_editing() {
let messages = vec![
TestMessageBuilder::new("My message", 1).outgoing().build(),
];
let messages = vec![TestMessageBuilder::new("My message", 1).outgoing().build()];
let mut app = TestAppBuilder::new()
.with_chats(vec![create_test_chat("Chat", 101)])
.selected_chat(101)
@@ -564,9 +546,7 @@ async fn test_normal_mode_auto_enters_selection_on_any_key() {
/// Полный цикл: Normal → i → набор текста → Esc → Normal
#[tokio::test]
async fn test_full_mode_cycle() {
let messages = vec![
TestMessageBuilder::new("Msg", 1).build(),
];
let messages = vec![TestMessageBuilder::new("Msg", 1).build()];
let mut app = TestAppBuilder::new()
.with_chats(vec![create_test_chat("Chat", 101)])
.selected_chat(101)
@@ -599,9 +579,9 @@ async fn test_full_mode_cycle() {
/// Полный цикл: Normal → r (reply) → набор → Enter (отправка) → остаёмся в Insert
#[tokio::test]
async fn test_reply_send_stays_insert() {
let messages = vec![
TestMessageBuilder::new("Question?", 1).sender("Friend").build(),
];
let messages = vec![TestMessageBuilder::new("Question?", 1)
.sender("Friend")
.build()];
let mut app = TestAppBuilder::new()
.with_chats(vec![create_test_chat("Chat", 101)])
.selected_chat(101)