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
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:
@@ -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());
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user