fixes
Some checks 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-01-29 01:22:57 +03:00
parent 68a2b7a982
commit 126c7482af
39 changed files with 2861 additions and 74 deletions

151
tests/delete_message.rs Normal file
View File

@@ -0,0 +1,151 @@
// Integration tests for delete message flow
mod helpers;
use helpers::fake_tdclient::FakeTdClient;
use helpers::test_data::TestMessageBuilder;
/// Test: Удаление сообщения убирает его из списка
#[test]
fn test_delete_message_removes_from_list() {
let mut client = FakeTdClient::new();
// Отправляем сообщение
let msg_id = client.send_message(123, "Delete me".to_string(), None);
// Проверяем что сообщение есть
assert_eq!(client.get_messages(123).len(), 1);
// Удаляем сообщение
client.delete_message(123, msg_id);
// Проверяем что удаление записалось
assert_eq!(client.deleted_messages().len(), 1);
assert_eq!(client.deleted_messages()[0], msg_id);
// Проверяем что сообщение удалено из списка
assert_eq!(client.get_messages(123).len(), 0);
}
/// Test: Удаление нескольких сообщений
#[test]
fn test_delete_multiple_messages() {
let mut client = FakeTdClient::new();
// Отправляем 3 сообщения
let msg1_id = client.send_message(123, "Message 1".to_string(), None);
let msg2_id = client.send_message(123, "Message 2".to_string(), None);
let msg3_id = client.send_message(123, "Message 3".to_string(), None);
assert_eq!(client.get_messages(123).len(), 3);
// Удаляем первое и третье
client.delete_message(123, msg1_id);
client.delete_message(123, msg3_id);
// Проверяем историю удалений
assert_eq!(client.deleted_messages().len(), 2);
assert_eq!(client.deleted_messages()[0], msg1_id);
assert_eq!(client.deleted_messages()[1], msg3_id);
// Проверяем что осталось только второе сообщение
let messages = client.get_messages(123);
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].id, msg2_id);
assert_eq!(messages[0].content, "Message 2");
}
/// Test: Удаление только своих сообщений (проверка через can_be_deleted_for_all_users)
#[test]
fn test_can_only_delete_own_messages_for_all() {
let mut client = FakeTdClient::new();
// Наше исходящее сообщение (можно удалить для всех)
let outgoing_msg = TestMessageBuilder::new("My message", 1)
.outgoing()
.build();
client = client.with_message(123, outgoing_msg);
// Входящее сообщение от собеседника (можно удалить только для себя)
let incoming_msg = TestMessageBuilder::new("Their message", 2)
.sender("Alice")
.build();
client = client.with_message(123, incoming_msg);
// Проверяем флаги удаления
let messages = client.get_messages(123);
assert_eq!(messages[0].can_be_deleted_for_all_users, true); // Наше
assert_eq!(messages[1].can_be_deleted_for_all_users, false); // Чужое
// Оба можно удалить для себя
assert_eq!(messages[0].can_be_deleted_only_for_self, true);
assert_eq!(messages[1].can_be_deleted_only_for_self, true);
}
/// Test: Удаление несуществующего сообщения (ничего не происходит)
#[test]
fn test_delete_nonexistent_message() {
let mut client = FakeTdClient::new();
// Отправляем одно сообщение
let msg_id = client.send_message(123, "Exists".to_string(), None);
assert_eq!(client.get_messages(123).len(), 1);
// Пытаемся удалить несуществующее
client.delete_message(123, 999);
// Удаление записалось в историю
assert_eq!(client.deleted_messages().len(), 1);
assert_eq!(client.deleted_messages()[0], 999);
// Но существующее сообщение осталось
let messages = client.get_messages(123);
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].id, msg_id);
}
/// Test: Подтверждение удаления (симуляция модалки)
/// FakeTdClient сразу удаляет, но в реальном App должна быть модалка подтверждения
#[test]
fn test_delete_with_confirmation_flow() {
let mut client = FakeTdClient::new();
let msg_id = client.send_message(123, "To delete".to_string(), None);
// Шаг 1: Пользователь нажал 'd' -> показывается модалка (в App)
// В FakeTdClient просто проверяем что сообщение ещё есть
assert_eq!(client.get_messages(123).len(), 1);
assert_eq!(client.deleted_messages().len(), 0);
// Шаг 2: Пользователь подтвердил 'y' -> удаляем
client.delete_message(123, msg_id);
// Проверяем что удалено
assert_eq!(client.get_messages(123).len(), 0);
assert_eq!(client.deleted_messages().len(), 1);
}
/// Test: Отмена удаления (Esc) - сообщение остаётся
#[test]
fn test_cancel_delete_keeps_message() {
let mut client = FakeTdClient::new();
let msg_id = client.send_message(123, "Keep me".to_string(), None);
// Шаг 1: Пользователь нажал 'd' -> показалась модалка
assert_eq!(client.get_messages(123).len(), 1);
// Шаг 2: Пользователь нажал 'Esc' -> НЕ вызываем delete_message
// Проверяем что сообщение осталось
assert_eq!(client.get_messages(123).len(), 1);
assert_eq!(client.deleted_messages().len(), 0);
// Сообщение на месте
let messages = client.get_messages(123);
assert_eq!(messages[0].id, msg_id);
assert_eq!(messages[0].content, "Keep me");
}

192
tests/drafts.rs Normal file
View File

@@ -0,0 +1,192 @@
// Integration tests for drafts flow
mod helpers;
use helpers::test_data::{create_test_chat, TestChatBuilder};
use std::collections::HashMap;
/// Простая структура для хранения черновиков (как в реальном App)
struct DraftManager {
drafts: HashMap<i64, String>, // chat_id -> draft text
}
impl DraftManager {
fn new() -> Self {
Self {
drafts: HashMap::new(),
}
}
/// Сохранить черновик для чата
fn save_draft(&mut self, chat_id: i64, text: String) {
if text.is_empty() {
self.drafts.remove(&chat_id);
} else {
self.drafts.insert(chat_id, text);
}
}
/// Получить черновик для чата
fn get_draft(&self, chat_id: i64) -> Option<&String> {
self.drafts.get(&chat_id)
}
/// Очистить черновик для чата
fn clear_draft(&mut self, chat_id: i64) {
self.drafts.remove(&chat_id);
}
/// Проверить есть ли черновик
fn has_draft(&self, chat_id: i64) -> bool {
self.drafts.contains_key(&chat_id)
}
}
/// Test: Переключение между чатами сохраняет текст
#[test]
fn test_switching_chats_saves_draft() {
let mut drafts = DraftManager::new();
// Пользователь в чате 123, начал печатать
let current_chat = 123;
let input_text = "Hello, this is a draft message";
// Перед переключением на другой чат - сохраняем
drafts.save_draft(current_chat, input_text.to_string());
// Переключаемся на чат 456
let _new_chat = 456;
// Проверяем что черновик для 123 сохранился
assert!(drafts.has_draft(123));
assert_eq!(drafts.get_draft(123).unwrap(), input_text);
// В новом чате 456 черновика нет
assert!(!drafts.has_draft(456));
}
/// Test: Возврат в чат восстанавливает текст
#[test]
fn test_returning_to_chat_restores_draft() {
let mut drafts = DraftManager::new();
// Сохраняем черновик в чате 123
drafts.save_draft(123, "Unfinished message".to_string());
// Переключились на другие чаты
// ...
// Возвращаемся в чат 123
let restored_text = drafts.get_draft(123);
assert!(restored_text.is_some());
assert_eq!(restored_text.unwrap(), "Unfinished message");
}
/// Test: Отправка сообщения удаляет черновик
#[test]
fn test_sending_message_clears_draft() {
let mut drafts = DraftManager::new();
// Сохранили черновик
drafts.save_draft(123, "Draft text".to_string());
assert!(drafts.has_draft(123));
// Пользователь отправил сообщение - очищаем черновик
drafts.clear_draft(123);
assert!(!drafts.has_draft(123));
assert_eq!(drafts.get_draft(123), None);
}
/// Test: Индикатор черновика в списке чатов
#[test]
fn test_draft_indicator_in_chat_list() {
let mut drafts = DraftManager::new();
// Создаём несколько чатов
let chat1 = create_test_chat("Mom", 123);
let chat2 = TestChatBuilder::new("Boss", 456)
.draft("Draft: Meeting notes")
.build();
let chat3 = create_test_chat("Friend", 789);
// В реальном App: chat.draft_text устанавливается из DraftManager
// Здесь просто проверяем что у chat2 есть draft_text поле
assert_eq!(chat2.draft_text.as_ref().unwrap(), "Draft: Meeting notes");
// Симулируем: пользователь набрал текст в чате 123
drafts.save_draft(123, "My draft".to_string());
// Проверяем что драфт есть
assert!(drafts.has_draft(123));
assert_eq!(drafts.get_draft(123).unwrap(), "My draft");
// В UI рядом с чатом 123 будет показываться индикатор/превью
// Например: "Mom" | "Draft: My draft"
}
/// Test: Множественные черновики в разных чатах
#[test]
fn test_multiple_drafts_in_different_chats() {
let mut drafts = DraftManager::new();
// Создаём черновики в 3 чатах
drafts.save_draft(123, "Draft for Mom".to_string());
drafts.save_draft(456, "Draft for Boss".to_string());
drafts.save_draft(789, "Draft for Friend".to_string());
// Проверяем что все сохранились
assert_eq!(drafts.get_draft(123).unwrap(), "Draft for Mom");
assert_eq!(drafts.get_draft(456).unwrap(), "Draft for Boss");
assert_eq!(drafts.get_draft(789).unwrap(), "Draft for Friend");
// Очищаем один
drafts.clear_draft(456);
// Проверяем что остальные на месте
assert!(drafts.has_draft(123));
assert!(!drafts.has_draft(456));
assert!(drafts.has_draft(789));
}
/// Test: Пустой текст не сохраняется как черновик
#[test]
fn test_empty_text_does_not_save_draft() {
let mut drafts = DraftManager::new();
// Пытаемся сохранить пустой черновик
drafts.save_draft(123, "".to_string());
// Не должен сохраниться
assert!(!drafts.has_draft(123));
// Сохраняем нормальный черновик
drafts.save_draft(123, "Text".to_string());
assert!(drafts.has_draft(123));
// Затем очищаем (сохраняем пустой)
drafts.save_draft(123, "".to_string());
// Черновик должен удалиться
assert!(!drafts.has_draft(123));
}
/// Test: Редактирование черновика
#[test]
fn test_editing_draft() {
let mut drafts = DraftManager::new();
// Сохраняем начальный черновик
drafts.save_draft(123, "First version".to_string());
assert_eq!(drafts.get_draft(123).unwrap(), "First version");
// Пользователь редактирует - сохраняем обновлённую версию
drafts.save_draft(123, "Second version".to_string());
assert_eq!(drafts.get_draft(123).unwrap(), "Second version");
// Ещё раз редактирует
drafts.save_draft(123, "Final version".to_string());
assert_eq!(drafts.get_draft(123).unwrap(), "Final version");
}

152
tests/edit_message.rs Normal file
View File

@@ -0,0 +1,152 @@
// Integration tests for edit message flow
mod helpers;
use helpers::fake_tdclient::FakeTdClient;
use helpers::test_data::TestMessageBuilder;
/// Test: Редактирование сообщения изменяет текст
#[test]
fn test_edit_message_changes_text() {
let mut client = FakeTdClient::new();
// Отправляем сообщение
let msg_id = client.send_message(123, "Original text".to_string(), None);
// Редактируем сообщение
client.edit_message(123, msg_id, "Edited text".to_string());
// Проверяем что редактирование записалось
assert_eq!(client.edited_messages().len(), 1);
assert_eq!(client.edited_messages()[0].message_id, msg_id);
assert_eq!(client.edited_messages()[0].new_text, "Edited text");
// Проверяем что текст сообщения изменился
let messages = client.get_messages(123);
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].content, "Edited text");
}
/// Test: Редактирование устанавливает edit_date
#[test]
fn test_edit_message_sets_edit_date() {
let mut client = FakeTdClient::new();
// Отправляем сообщение
let msg_id = client.send_message(123, "Original".to_string(), None);
// Получаем дату до редактирования
let messages_before = client.get_messages(123);
let date_before = messages_before[0].date;
assert_eq!(messages_before[0].edit_date, 0); // Не редактировалось
// Редактируем сообщение
client.edit_message(123, msg_id, "Edited".to_string());
// Проверяем что edit_date установлена
let messages_after = client.get_messages(123);
assert!(messages_after[0].edit_date > 0);
assert!(messages_after[0].edit_date > date_before); // edit_date после date
}
/// Test: Редактирование только своих сообщений (проверка через can_be_edited)
#[test]
fn test_can_only_edit_own_messages() {
let mut client = FakeTdClient::new();
// Наше исходящее сообщение (можно редактировать)
let outgoing_msg = TestMessageBuilder::new("My message", 1)
.outgoing()
.build();
client = client.with_message(123, outgoing_msg);
// Входящее сообщение от собеседника (нельзя редактировать)
let incoming_msg = TestMessageBuilder::new("Their message", 2)
.sender("Alice")
.build();
client = client.with_message(123, incoming_msg);
// Проверяем флаги
let messages = client.get_messages(123);
assert_eq!(messages[0].can_be_edited, true); // Наше сообщение
assert_eq!(messages[1].can_be_edited, false); // Чужое сообщение
}
/// Test: Множественные редактирования одного сообщения
#[test]
fn test_multiple_edits_of_same_message() {
let mut client = FakeTdClient::new();
let msg_id = client.send_message(123, "Version 1".to_string(), None);
// Первое редактирование
client.edit_message(123, msg_id, "Version 2".to_string());
// Второе редактирование
client.edit_message(123, msg_id, "Version 3".to_string());
// Третье редактирование
client.edit_message(123, msg_id, "Final version".to_string());
// Проверяем что все 3 редактирования записаны
assert_eq!(client.edited_messages().len(), 3);
assert_eq!(client.edited_messages()[0].new_text, "Version 2");
assert_eq!(client.edited_messages()[1].new_text, "Version 3");
assert_eq!(client.edited_messages()[2].new_text, "Final version");
// Проверяем что сообщение содержит последнюю версию
let messages = client.get_messages(123);
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].content, "Final version");
}
/// Test: Редактирование несуществующего сообщения (ничего не происходит)
#[test]
fn test_edit_nonexistent_message() {
let mut client = FakeTdClient::new();
// Пытаемся отредактировать несуществующее сообщение
client.edit_message(123, 999, "New text".to_string());
// Редактирование записалось в историю (FakeTdClient всё записывает)
assert_eq!(client.edited_messages().len(), 1);
// Но в списке сообщений ничего нет
let messages = client.get_messages(123);
assert_eq!(messages.len(), 0);
}
/// Test: Отмена редактирования (Esc) - тестируем что можно восстановить original
/// В данном случае проверяем что FakeTdClient сохраняет историю edits
#[test]
fn test_edit_history_tracking() {
let mut client = FakeTdClient::new();
let msg_id = client.send_message(123, "Original".to_string(), None);
// Симулируем начало редактирования -> изменение -> отмена
// Отменять на уровне FakeTdClient нельзя, но можно проверить что original сохранён
// Сохраняем original
let messages_before = client.get_messages(123);
let original = messages_before[0].content.clone();
// Редактируем
client.edit_message(123, msg_id, "Edited".to_string());
// Проверяем что изменилось
let messages_edited = client.get_messages(123);
assert_eq!(messages_edited[0].content, "Edited");
// Можем "отменить" редактирование вернув original
client.edit_message(123, msg_id, original);
// Проверяем что вернулось
let messages_restored = client.get_messages(123);
assert_eq!(messages_restored[0].content, "Original");
// История показывает 2 редактирования
assert_eq!(client.edited_messages().len(), 2);
}

116
tests/footer.rs Normal file
View File

@@ -0,0 +1,116 @@
// Footer UI snapshot tests
mod helpers;
use helpers::test_data::create_test_chat;
use helpers::app_builder::TestAppBuilder;
use helpers::snapshot_utils::{render_to_buffer, buffer_to_string};
use insta::assert_snapshot;
use tele_tui::tdlib::NetworkState;
#[test]
fn snapshot_footer_chat_list() {
let chat = create_test_chat("Mom", 123);
let app = TestAppBuilder::new()
.with_chat(chat)
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::footer::render(f, f.area(), &app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("footer_chat_list", output);
}
#[test]
fn snapshot_footer_open_chat() {
let chat = create_test_chat("Mom", 123);
let mut app = TestAppBuilder::new()
.with_chat(chat)
.selected_chat(123)
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::footer::render(f, f.area(), &app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("footer_open_chat", output);
}
#[test]
fn snapshot_footer_network_waiting() {
let chat = create_test_chat("Mom", 123);
let mut app = TestAppBuilder::new()
.with_chat(chat)
.build();
// Set network state to WaitingForNetwork
app.td_client.network_state = NetworkState::WaitingForNetwork;
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::footer::render(f, f.area(), &app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("footer_network_waiting", output);
}
#[test]
fn snapshot_footer_network_connecting_proxy() {
let chat = create_test_chat("Mom", 123);
let mut app = TestAppBuilder::new()
.with_chat(chat)
.build();
// Set network state to ConnectingToProxy
app.td_client.network_state = NetworkState::ConnectingToProxy;
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::footer::render(f, f.area(), &app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("footer_network_connecting_proxy", output);
}
#[test]
fn snapshot_footer_network_connecting() {
let chat = create_test_chat("Mom", 123);
let mut app = TestAppBuilder::new()
.with_chat(chat)
.build();
// Set network state to Connecting
app.td_client.network_state = NetworkState::Connecting;
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::footer::render(f, f.area(), &app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("footer_network_connecting", output);
}
#[test]
fn snapshot_footer_search_mode() {
let chat = create_test_chat("Mom", 123);
let mut app = TestAppBuilder::new()
.with_chat(chat)
.searching("query")
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::footer::render(f, f.area(), &app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("footer_search_mode", output);
}

View File

@@ -3,6 +3,7 @@
use tele_tui::app::{App, AppScreen};
use tele_tui::config::Config;
use tele_tui::tdlib::{ChatInfo, MessageInfo};
use tele_tui::tdlib::client::AuthState;
use ratatui::widgets::ListState;
use std::collections::HashMap;
@@ -31,6 +32,11 @@ pub struct TestAppBuilder {
message_search_query: String,
forwarding_message_id: Option<i64>,
is_selecting_forward_chat: bool,
status_message: Option<String>,
auth_state: Option<AuthState>,
phone_input: Option<String>,
code_input: Option<String>,
password_input: Option<String>,
}
impl Default for TestAppBuilder {
@@ -60,6 +66,11 @@ impl TestAppBuilder {
message_search_query: String::new(),
forwarding_message_id: None,
is_selecting_forward_chat: false,
status_message: None,
auth_state: None,
phone_input: None,
code_input: None,
password_input: None,
}
}
@@ -168,6 +179,36 @@ impl TestAppBuilder {
self
}
/// Установить статус сообщение (для loading screen)
pub fn status_message(mut self, message: &str) -> Self {
self.status_message = Some(message.to_string());
self
}
/// Установить auth state
pub fn auth_state(mut self, state: AuthState) -> Self {
self.auth_state = Some(state);
self
}
/// Установить phone input
pub fn phone_input(mut self, phone: &str) -> Self {
self.phone_input = Some(phone.to_string());
self
}
/// Установить code input
pub fn code_input(mut self, code: &str) -> Self {
self.code_input = Some(code.to_string());
self
}
/// Установить password input
pub fn password_input(mut self, password: &str) -> Self {
self.password_input = Some(password.to_string());
self
}
/// Построить App
///
/// ВАЖНО: Этот метод создаёт App с реальным TdClient,
@@ -193,6 +234,27 @@ impl TestAppBuilder {
app.forwarding_message_id = self.forwarding_message_id;
app.is_selecting_forward_chat = self.is_selecting_forward_chat;
// Применяем status_message
if let Some(status) = self.status_message {
app.status_message = Some(status);
}
// Применяем auth state
if let Some(auth_state) = self.auth_state {
app.td_client.auth_state = auth_state;
}
// Применяем auth inputs
if let Some(phone) = self.phone_input {
app.phone_input = phone;
}
if let Some(code) = self.code_input {
app.code_input = code;
}
if let Some(password) = self.password_input {
app.password_input = password;
}
// Выбираем первый чат если есть
if !app.chats.is_empty() {
let mut list_state = ListState::default();

149
tests/input_field.rs Normal file
View File

@@ -0,0 +1,149 @@
// Input Field UI snapshot tests
mod helpers;
use helpers::test_data::{TestMessageBuilder, create_test_chat};
use helpers::app_builder::TestAppBuilder;
use helpers::snapshot_utils::{render_to_buffer, buffer_to_string};
use insta::assert_snapshot;
#[test]
fn snapshot_empty_input() {
let chat = create_test_chat("Mom", 123);
let app = TestAppBuilder::new()
.with_chat(chat)
.selected_chat(123)
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::messages::render(f, f.area(), &app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("empty_input", output);
}
#[test]
fn snapshot_input_with_text() {
let chat = create_test_chat("Mom", 123);
let app = TestAppBuilder::new()
.with_chat(chat)
.selected_chat(123)
.message_input("Hello, how are you?")
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::messages::render(f, f.area(), &app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("input_with_text", output);
}
#[test]
fn snapshot_input_long_text_2_lines() {
let chat = create_test_chat("Mom", 123);
// Text that wraps to 2 lines
let long_text = "This is a longer message that will wrap to multiple lines in the input field for testing purposes.";
let app = TestAppBuilder::new()
.with_chat(chat)
.selected_chat(123)
.message_input(long_text)
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::messages::render(f, f.area(), &app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("input_long_text_2_lines", output);
}
#[test]
fn snapshot_input_long_text_max_lines() {
let chat = create_test_chat("Mom", 123);
// Very long text that reaches maximum 10 lines
let very_long_text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.";
let app = TestAppBuilder::new()
.with_chat(chat)
.selected_chat(123)
.message_input(very_long_text)
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::messages::render(f, f.area(), &app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("input_long_text_max_lines", output);
}
#[test]
fn snapshot_input_editing_mode() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("Original message text", 1)
.outgoing()
.build();
let app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.selected_chat(123)
.editing_message(1)
.message_input("Edited text here")
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::messages::render(f, f.area(), &app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("input_editing_mode", output);
}
#[test]
fn snapshot_input_reply_mode() {
let chat = create_test_chat("Mom", 123);
let original_msg = TestMessageBuilder::new("What do you think about this?", 1)
.sender("Mom")
.build();
let app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, original_msg)
.selected_chat(123)
.replying_to(1)
.message_input("I think it's great!")
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::messages::render(f, f.area(), &app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("input_reply_mode", output);
}
#[test]
fn snapshot_input_search_mode() {
let chat = create_test_chat("Mom", 123);
let mut app = TestAppBuilder::new()
.with_chat(chat)
.selected_chat(123)
.searching("hello")
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::chat_list::render(f, f.area(), &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("input_search_mode", output);
}

230
tests/navigation.rs Normal file
View File

@@ -0,0 +1,230 @@
// Integration tests for navigation flow
mod helpers;
use helpers::fake_tdclient::FakeTdClient;
use helpers::test_data::{create_test_chat, TestMessageBuilder};
/// Test: Навигация вверх/вниз по списку чатов
#[test]
fn test_navigate_chat_list_up_down() {
let mut client = FakeTdClient::new();
let chat1 = create_test_chat("Mom", 123);
let chat2 = create_test_chat("Boss", 456);
let chat3 = create_test_chat("Friend", 789);
client = client.with_chats(vec![chat1, chat2, chat3]);
let chats = client.get_chats();
// Начинаем с индекса 0
let mut selected_index = 0;
assert_eq!(chats[selected_index].title, "Mom");
// ↓ - вниз
selected_index = (selected_index + 1).min(chats.len() - 1);
assert_eq!(selected_index, 1);
assert_eq!(chats[selected_index].title, "Boss");
// ↓ - ещё вниз
selected_index = (selected_index + 1).min(chats.len() - 1);
assert_eq!(selected_index, 2);
assert_eq!(chats[selected_index].title, "Friend");
// ↓ - на границе (не должно выйти за пределы)
selected_index = (selected_index + 1).min(chats.len() - 1);
assert_eq!(selected_index, 2); // Остался на последнем
// ↑ - вверх
selected_index = selected_index.saturating_sub(1);
assert_eq!(selected_index, 1);
assert_eq!(chats[selected_index].title, "Boss");
// ↑ - ещё вверх
selected_index = selected_index.saturating_sub(1);
assert_eq!(selected_index, 0);
assert_eq!(chats[selected_index].title, "Mom");
// ↑ - на границе (не должно выйти за пределы)
selected_index = selected_index.saturating_sub(1);
assert_eq!(selected_index, 0); // Остался на первом
}
/// Test: Enter открывает чат
#[test]
fn test_enter_opens_chat() {
let mut client = FakeTdClient::new();
let chat = create_test_chat("Mom", 123);
let _client = client.with_chat(chat);
// Состояние: список чатов, выбран чат 123
let selected_chat_id: Option<i64> = None;
// Пользователь нажал Enter
let new_selected_chat_id = Some(123);
assert_eq!(selected_chat_id, None);
assert_eq!(new_selected_chat_id, Some(123));
}
/// Test: Esc закрывает чат
#[test]
fn test_esc_closes_chat() {
// Состояние: открыт чат 123
let selected_chat_id = Some(123);
// Пользователь нажал Esc
let selected_chat_id: Option<i64> = None;
assert_eq!(selected_chat_id, None);
}
/// Test: Скролл сообщений в чате
#[test]
fn test_scroll_messages_in_chat() {
let mut client = FakeTdClient::new();
let messages = vec![
TestMessageBuilder::new("Msg 1", 1).build(),
TestMessageBuilder::new("Msg 2", 2).build(),
TestMessageBuilder::new("Msg 3", 3).build(),
TestMessageBuilder::new("Msg 4", 4).build(),
TestMessageBuilder::new("Msg 5", 5).build(),
];
client = client.with_messages(123, messages);
let msgs = client.get_messages(123);
// Скролл начинается снизу (последнее сообщение видно)
let mut scroll_offset: usize = 0;
// ↑ - скролл вверх (увеличиваем offset)
scroll_offset += 1;
assert_eq!(scroll_offset, 1);
// ↑ - ещё вверх
scroll_offset += 1;
assert_eq!(scroll_offset, 2);
// ↓ - скролл вниз (уменьшаем offset)
scroll_offset = scroll_offset.saturating_sub(1);
assert_eq!(scroll_offset, 1);
// ↓ - к низу
scroll_offset = scroll_offset.saturating_sub(1);
assert_eq!(scroll_offset, 0);
// ↓ - на границе
scroll_offset = scroll_offset.saturating_sub(1);
assert_eq!(scroll_offset, 0); // Не уходим в минус
}
/// Test: Переключение между папками (1-9)
#[test]
fn test_switch_folders() {
let mut client = FakeTdClient::new();
// Добавляем папки (FakeTdClient уже создаёт "All" с id=0)
client = client
.with_folder(1, "Personal")
.with_folder(2, "Work");
let folders = client.get_folders();
// Проверяем что папки на месте
assert_eq!(folders.len(), 3);
assert_eq!(folders[0].name, "All");
assert_eq!(folders[1].name, "Personal");
assert_eq!(folders[2].name, "Work");
// Начинаем с папки 0 (All)
let mut selected_folder_index = 0;
assert_eq!(folders[selected_folder_index].name, "All");
// Нажали '1' - папка Personal (индекс 1)
selected_folder_index = 1;
assert_eq!(folders[selected_folder_index].name, "Personal");
// Нажали '2' - папка Work (индекс 2)
selected_folder_index = 2;
assert_eq!(folders[selected_folder_index].name, "Work");
// Нажали '0' (или Esc из папки) - обратно в All (индекс 0)
selected_folder_index = 0;
assert_eq!(folders[selected_folder_index].name, "All");
}
/// Test: Русская раскладка для навигации (р/о/л/д)
#[test]
fn test_russian_layout_navigation() {
// В реальном App: к/j/h/l маппятся на р/о/л/д для русской раскладки
// Mapping:
// j (down) <-> о
// k (up) <-> л
// h (left) <-> р
// l (right) <-> д
let mut selected_index = 1;
// Симулируем нажатие 'о' (как 'j' - вниз)
selected_index += 1;
assert_eq!(selected_index, 2);
// 'л' (как 'k' - вверх)
selected_index -= 1;
assert_eq!(selected_index, 1);
// Проверяем что логика работает одинаково
assert!(true); // Реальный тест был бы в input handler
}
/// Test: Подгрузка старых сообщений при скролле вверх
#[test]
fn test_load_older_messages_on_scroll_up() {
let mut client = FakeTdClient::new();
// Начальные сообщения (последние 10)
let initial_messages = vec![
TestMessageBuilder::new("Msg 91", 91).build(),
TestMessageBuilder::new("Msg 92", 92).build(),
TestMessageBuilder::new("Msg 93", 93).build(),
TestMessageBuilder::new("Msg 94", 94).build(),
TestMessageBuilder::new("Msg 95", 95).build(),
TestMessageBuilder::new("Msg 96", 96).build(),
TestMessageBuilder::new("Msg 97", 97).build(),
TestMessageBuilder::new("Msg 98", 98).build(),
TestMessageBuilder::new("Msg 99", 99).build(),
TestMessageBuilder::new("Msg 100", 100).build(),
];
client = client.with_messages(123, initial_messages);
assert_eq!(client.get_messages(123).len(), 10);
// Пользователь скроллит до самого верха (дошёл до Msg 91)
// Триггерим подгрузку старых сообщений
// Симулируем подгрузку (добавляем старые сообщения в начало)
let older_messages = vec![
TestMessageBuilder::new("Msg 81", 81).build(),
TestMessageBuilder::new("Msg 82", 82).build(),
TestMessageBuilder::new("Msg 83", 83).build(),
TestMessageBuilder::new("Msg 84", 84).build(),
TestMessageBuilder::new("Msg 85", 85).build(),
];
// Добавляем к существующим (в реальности - prepend)
let mut all_messages = older_messages;
all_messages.extend(client.get_messages(123));
client.messages.insert(123, all_messages);
// Теперь должно быть 15 сообщений
assert_eq!(client.get_messages(123).len(), 15);
assert_eq!(client.get_messages(123)[0].content, "Msg 81");
assert_eq!(client.get_messages(123)[14].content, "Msg 100");
}

168
tests/network_typing.rs Normal file
View File

@@ -0,0 +1,168 @@
// Integration tests for network and typing flow
mod helpers;
use helpers::fake_tdclient::FakeTdClient;
use helpers::test_data::create_test_chat;
use tele_tui::tdlib::NetworkState;
/// Test: Смена состояния сети отображается в UI
#[test]
fn test_network_state_changes() {
let mut client = FakeTdClient::new();
// Начальное состояние - Ready
assert_eq!(client.network_state, NetworkState::Ready);
// Сеть пропала
client.network_state = NetworkState::WaitingForNetwork;
assert_eq!(client.network_state, NetworkState::WaitingForNetwork);
// В UI: "⚠ Нет сети"
// Подключаемся к прокси
client.network_state = NetworkState::ConnectingToProxy;
assert_eq!(client.network_state, NetworkState::ConnectingToProxy);
// В UI: "⏳ Прокси..."
// Подключаемся к серверам
client.network_state = NetworkState::Connecting;
assert_eq!(client.network_state, NetworkState::Connecting);
// В UI: "⏳ Подключение..."
// Соединение восстановлено
client.network_state = NetworkState::Ready;
assert_eq!(client.network_state, NetworkState::Ready);
// В UI: индикатор скрывается
}
/// Test: WaitingForNetwork - нет подключения
#[test]
fn test_network_waiting_for_network() {
let mut client = FakeTdClient::new();
client.network_state = NetworkState::WaitingForNetwork;
assert_eq!(client.network_state, NetworkState::WaitingForNetwork);
// В этом состоянии:
// - Показывается предупреждение "⚠ Нет сети"
// - Отправка сообщений заблокирована
// - Updates не приходят
}
/// Test: ConnectingToProxy - подключение через прокси
#[test]
fn test_network_connecting_to_proxy() {
let mut client = FakeTdClient::new();
client.network_state = NetworkState::ConnectingToProxy;
assert_eq!(client.network_state, NetworkState::ConnectingToProxy);
// В UI: "⏳ Прокси..."
}
/// Test: Connecting - подключение к серверам Telegram
#[test]
fn test_network_connecting() {
let mut client = FakeTdClient::new();
client.network_state = NetworkState::Connecting;
assert_eq!(client.network_state, NetworkState::Connecting);
// В UI: "⏳ Подключение..."
}
/// Test: Updating - обновление данных
#[test]
fn test_network_updating() {
let mut client = FakeTdClient::new();
client.network_state = NetworkState::Updating;
assert_eq!(client.network_state, NetworkState::Updating);
// В UI: "⏳ Обновление..."
}
/// Test: Typing indicator - пользователь печатает
#[test]
fn test_typing_indicator_on() {
let mut client = FakeTdClient::new();
let chat = create_test_chat("Alice", 123);
client = client.with_chat(chat);
// Alice начала печатать в чате 123
client.set_typing(Some(123));
assert_eq!(client.typing_chat_id, Some(123));
// В UI: под сообщениями отображается "Alice печатает..."
}
/// Test: Typing indicator - пользователь перестал печатать
#[test]
fn test_typing_indicator_off() {
let mut client = FakeTdClient::new();
// Изначально Alice печатала
client.set_typing(Some(123));
assert_eq!(client.typing_chat_id, Some(123));
// Alice перестала печатать
client.set_typing(None);
assert_eq!(client.typing_chat_id, None);
// В UI: индикатор "печатает..." исчезает
}
/// Test: Отправка своего typing status
#[test]
fn test_send_own_typing_status() {
let mut client = FakeTdClient::new();
// Пользователь начал печатать в чате 456
// В реальном App вызывается client.send_chat_action(chat_id, ChatAction::Typing)
// Симулируем: устанавливаем что мы печатаем
let our_typing_chat_id = Some(456);
assert_eq!(our_typing_chat_id, Some(456));
// Собеседник видит что мы печатаем
// Через некоторое время (или при отправке сообщения) - отменяем
// client.send_chat_action(chat_id, ChatAction::Cancel)
let our_typing_chat_id: Option<i64> = None;
assert_eq!(our_typing_chat_id, None);
}
/// Test: Множественные переходы состояний сети
#[test]
fn test_multiple_network_state_transitions() {
let mut client = FakeTdClient::new();
// Цикл переходов состояний
let states = vec![
NetworkState::Ready,
NetworkState::Connecting,
NetworkState::Ready,
NetworkState::WaitingForNetwork,
NetworkState::ConnectingToProxy,
NetworkState::Connecting,
NetworkState::Updating,
NetworkState::Ready,
];
for state in states {
client.network_state = state.clone();
assert_eq!(client.network_state, state);
}
// Финальное состояние - Ready
assert_eq!(client.network_state, NetworkState::Ready);
}

133
tests/profile.rs Normal file
View File

@@ -0,0 +1,133 @@
// Integration tests for profile flow
mod helpers;
use helpers::fake_tdclient::FakeTdClient;
use helpers::test_data::create_test_chat;
use tele_tui::tdlib::ProfileInfo;
/// Test: Открытие профиля в личном чате (i)
#[test]
fn test_open_profile_in_private_chat() {
let client = FakeTdClient::new();
let chat = create_test_chat("Alice", 123);
let _client = client.with_chat(chat);
// Пользователь открыл чат и нажал 'i'
let profile_mode = true;
assert!(profile_mode);
// В реальном App загрузится ProfileInfo для этого чата
}
/// Test: Профиль показывает имя, username, телефон
#[test]
fn test_profile_shows_user_info() {
let profile = ProfileInfo {
chat_id: 123,
title: "Alice Johnson".to_string(),
username: Some("alice".to_string()),
phone_number: Some("+1234567890".to_string()),
bio: None,
chat_type: "Личный чат".to_string(),
member_count: None,
description: None,
invite_link: None,
is_group: false,
online_status: Some("Online".to_string()),
};
assert_eq!(profile.title, "Alice Johnson");
assert_eq!(profile.username.as_ref().unwrap(), "alice");
assert_eq!(profile.phone_number.as_ref().unwrap(), "+1234567890");
assert_eq!(profile.chat_type, "Личный чат");
}
/// Test: Профиль в группе показывает количество участников
#[test]
fn test_profile_shows_group_member_count() {
let profile = ProfileInfo {
chat_id: 456,
title: "Work Team".to_string(),
username: None,
phone_number: None,
bio: Some("Our work group".to_string()),
chat_type: "Группа".to_string(),
member_count: Some(25),
description: None,
invite_link: None,
is_group: true,
online_status: None,
};
assert_eq!(profile.title, "Work Team");
assert_eq!(profile.chat_type, "Группа");
assert_eq!(profile.member_count, Some(25));
assert_eq!(profile.bio.as_ref().unwrap(), "Our work group");
}
/// Test: Профиль в канале
#[test]
fn test_profile_shows_channel_info() {
let profile = ProfileInfo {
chat_id: 789,
title: "News Channel".to_string(),
username: Some("news_channel".to_string()),
phone_number: None,
bio: Some("Latest news updates".to_string()),
chat_type: "Канал".to_string(),
member_count: Some(1000),
description: Some("Latest news updates".to_string()),
invite_link: Some("t.me/news_channel".to_string()),
is_group: false,
online_status: None,
};
assert_eq!(profile.title, "News Channel");
assert_eq!(profile.username.as_ref().unwrap(), "news_channel");
assert_eq!(profile.chat_type, "Канал");
assert_eq!(profile.member_count, Some(1000)); // Subscribers
}
/// Test: Закрытие профиля (Esc)
#[test]
fn test_close_profile_with_esc() {
// Профиль открыт
let profile_mode = true;
// Пользователь нажал Esc
let profile_mode = false;
assert!(!profile_mode);
}
/// Test: Профиль без username и phone
#[test]
fn test_profile_without_optional_fields() {
let profile = ProfileInfo {
chat_id: 999,
title: "Anonymous User".to_string(),
username: None,
phone_number: None,
bio: None,
chat_type: "Личный чат".to_string(),
member_count: None,
description: None,
invite_link: None,
is_group: false,
online_status: None,
};
// Обязательные поля заполнены
assert_eq!(profile.title, "Anonymous User");
assert_eq!(profile.chat_type, "Личный чат");
// Опциональные поля None
assert_eq!(profile.username, None);
assert_eq!(profile.phone_number, None);
assert_eq!(profile.bio, None);
// В UI будут отображаться только доступные поля
}

243
tests/reactions.rs Normal file
View File

@@ -0,0 +1,243 @@
// Integration tests for reactions flow
mod helpers;
use helpers::fake_tdclient::FakeTdClient;
use helpers::test_data::TestMessageBuilder;
/// Test: Добавление реакции к сообщению
#[test]
fn test_add_reaction_to_message() {
let mut client = FakeTdClient::new();
// Отправляем сообщение
let msg_id = client.send_message(123, "React to this!".to_string(), None);
// Добавляем реакцию
client.add_reaction(msg_id, "👍".to_string());
// Проверяем что реакция записалась
let reactions = client.reactions.get(&msg_id);
assert!(reactions.is_some());
assert_eq!(reactions.unwrap().len(), 1);
assert_eq!(reactions.unwrap()[0], "👍");
}
/// Test: Удаление реакции (toggle) - вторичное нажатие
#[test]
fn test_toggle_reaction_removes_it() {
let mut client = FakeTdClient::new();
// Создаём сообщение с нашей реакцией
let msg = TestMessageBuilder::new("Message", 100)
.reaction("👍", 1, true) // chosen=true - наша реакция
.build();
client = client.with_message(123, msg);
// Проверяем что реакция есть
let messages_before = client.get_messages(123);
assert_eq!(messages_before[0].reactions.len(), 1);
assert_eq!(messages_before[0].reactions[0].is_chosen, true);
// Симулируем удаление реакции (в реальном App это toggle)
// FakeTdClient просто записывает что реакция была "убрана"
// Для теста можем удалить из списка вручную или расширить FakeTdClient
// Создаём сообщение без реакции (после toggle)
let msg_after = TestMessageBuilder::new("Message", 100).build();
// Заменяем в клиенте
client.messages.insert(123, vec![msg_after]);
let messages_after = client.get_messages(123);
assert_eq!(messages_after[0].reactions.len(), 0);
}
/// Test: Множественные реакции на одно сообщение
#[test]
fn test_multiple_reactions_on_one_message() {
let mut client = FakeTdClient::new();
let msg_id = client.send_message(123, "Many reactions".to_string(), None);
// Добавляем несколько разных реакций
client.add_reaction(msg_id, "👍".to_string());
client.add_reaction(msg_id, "❤️".to_string());
client.add_reaction(msg_id, "😂".to_string());
client.add_reaction(msg_id, "🔥".to_string());
// Проверяем что все 4 реакции записались
let reactions = client.reactions.get(&msg_id).unwrap();
assert_eq!(reactions.len(), 4);
assert_eq!(reactions[0], "👍");
assert_eq!(reactions[1], "❤️");
assert_eq!(reactions[2], "😂");
assert_eq!(reactions[3], "🔥");
}
/// Test: Реакции от разных пользователей (count > 1)
#[test]
fn test_reactions_from_multiple_users() {
let mut client = FakeTdClient::new();
// Создаём сообщение с реакцией от 3 пользователей
let msg = TestMessageBuilder::new("Popular message", 100)
.reaction("👍", 3, false) // 3 человека, но не мы
.build();
client = client.with_message(123, msg);
let messages = client.get_messages(123);
let reaction = &messages[0].reactions[0];
assert_eq!(reaction.emoji, "👍");
assert_eq!(reaction.count, 3);
assert_eq!(reaction.is_chosen, false);
}
/// Test: Своя реакция (is_chosen = true)
#[test]
fn test_own_reaction_is_chosen() {
let mut client = FakeTdClient::new();
// Создаём сообщение с нашей реакцией
let msg = TestMessageBuilder::new("I reacted", 100)
.reaction("❤️", 1, true) // chosen=true
.build();
client = client.with_message(123, msg);
let messages = client.get_messages(123);
let reaction = &messages[0].reactions[0];
assert_eq!(reaction.is_chosen, true);
// В UI это будет отображаться в рамках: [❤️]
}
/// Test: Чужая реакция (is_chosen = false)
#[test]
fn test_other_reaction_not_chosen() {
let mut client = FakeTdClient::new();
// Создаём сообщение с чужой реакцией
let msg = TestMessageBuilder::new("They reacted", 100)
.reaction("😂", 2, false) // chosen=false
.build();
client = client.with_message(123, msg);
let messages = client.get_messages(123);
let reaction = &messages[0].reactions[0];
assert_eq!(reaction.is_chosen, false);
// В UI это будет отображаться без рамок: 😂 2
}
/// Test: Счётчик реакций увеличивается
#[test]
fn test_reaction_counter_increases() {
let mut client = FakeTdClient::new();
// Начальное сообщение с 1 реакцией
let msg_v1 = TestMessageBuilder::new("Growing", 100)
.reaction("👍", 1, false)
.build();
client = client.with_message(123, msg_v1);
// Симулируем обновление: теперь 5 человек
let msg_v2 = TestMessageBuilder::new("Growing", 100)
.reaction("👍", 5, false)
.build();
client.messages.insert(123, vec![msg_v2]);
let messages = client.get_messages(123);
assert_eq!(messages[0].reactions[0].count, 5);
}
/// Test: Обновление реакции - мы добавили свою к существующим
#[test]
fn test_update_reaction_we_add_ours() {
let mut client = FakeTdClient::new();
// Изначально: 2 человека, но не мы
let msg_before = TestMessageBuilder::new("Update", 100)
.reaction("🔥", 2, false)
.build();
client = client.with_message(123, msg_before);
// После добавления нашей: 3 человека, в том числе мы
let msg_after = TestMessageBuilder::new("Update", 100)
.reaction("🔥", 3, true) // is_chosen=true теперь
.build();
client.messages.insert(123, vec![msg_after]);
let messages = client.get_messages(123);
let reaction = &messages[0].reactions[0];
assert_eq!(reaction.count, 3);
assert_eq!(reaction.is_chosen, true);
}
/// Test: Реакция с count=1 отображается только emoji
#[test]
fn test_single_reaction_shows_only_emoji() {
let mut client = FakeTdClient::new();
let msg = TestMessageBuilder::new("Single", 100)
.reaction("❤️", 1, true)
.build();
client = client.with_message(123, msg);
let messages = client.get_messages(123);
let reaction = &messages[0].reactions[0];
assert_eq!(reaction.count, 1);
// В UI: если count=1, показываем только emoji без цифры
// Логика рендеринга: count > 1 ? "emoji count" : "emoji"
}
/// Test: Реакции на несколько сообщений
#[test]
fn test_reactions_on_multiple_messages() {
let mut client = FakeTdClient::new();
let msg1 = TestMessageBuilder::new("First", 100)
.reaction("👍", 2, false)
.build();
let msg2 = TestMessageBuilder::new("Second", 101)
.reaction("❤️", 1, true)
.build();
let msg3 = TestMessageBuilder::new("Third", 102)
.reaction("😂", 5, false)
.reaction("🔥", 3, true) // Две разные реакции
.build();
client = client
.with_message(123, msg1)
.with_message(123, msg2)
.with_message(123, msg3);
let messages = client.get_messages(123);
// Первое: 1 реакция
assert_eq!(messages[0].reactions.len(), 1);
assert_eq!(messages[0].reactions[0].emoji, "👍");
// Второе: 1 реакция
assert_eq!(messages[1].reactions.len(), 1);
assert_eq!(messages[1].reactions[0].emoji, "❤️");
// Третье: 2 реакции
assert_eq!(messages[2].reactions.len(), 2);
assert_eq!(messages[2].reactions[0].emoji, "😂");
assert_eq!(messages[2].reactions[1].emoji, "🔥");
assert_eq!(messages[2].reactions[1].is_chosen, true);
}

202
tests/reply_forward.rs Normal file
View File

@@ -0,0 +1,202 @@
// Integration tests for reply and forward flow
mod helpers;
use helpers::fake_tdclient::FakeTdClient;
use helpers::test_data::TestMessageBuilder;
use tele_tui::tdlib::{ForwardInfo, ReplyInfo};
/// Test: Reply создаёт сообщение с reply_to
#[test]
fn test_reply_creates_message_with_reply_to() {
let mut client = FakeTdClient::new();
// Входящее сообщение от собеседника
let original_msg = TestMessageBuilder::new("Question?", 100)
.sender("Alice")
.build();
client = client.with_message(123, original_msg);
// Отвечаем на него
let reply_id = client.send_message(123, "Answer!".to_string(), Some(100));
// Проверяем что ответ отправлен с reply_to
assert_eq!(client.sent_messages().len(), 1);
assert_eq!(client.sent_messages()[0].reply_to, Some(100));
// Проверяем что в списке 2 сообщения
let messages = client.get_messages(123);
assert_eq!(messages.len(), 2);
assert_eq!(messages[1].id, reply_id);
assert_eq!(messages[1].content, "Answer!");
}
/// Test: Reply отображает превью оригинального сообщения
#[test]
fn test_reply_shows_original_preview() {
let mut client = FakeTdClient::new();
// Создаём сообщение с reply info
let reply_msg = TestMessageBuilder::new("Reply text", 101)
.outgoing()
.reply_to(100, "Alice", "Original")
.build();
client = client.with_message(123, reply_msg);
// Проверяем что reply_to сохранено
let messages = client.get_messages(123);
assert_eq!(messages.len(), 1);
assert!(messages[0].reply_to.is_some());
let reply = messages[0].reply_to.as_ref().unwrap();
assert_eq!(reply.message_id, 100);
assert_eq!(reply.sender_name, "Alice");
assert_eq!(reply.text, "Original");
}
/// Test: Отмена reply mode (Esc) - сообщение отправляется без reply_to
#[test]
fn test_cancel_reply_sends_without_reply_to() {
let mut client = FakeTdClient::new();
// Входящее сообщение
let original = TestMessageBuilder::new("Question?", 100)
.sender("Alice")
.build();
client = client.with_message(123, original);
// Пользователь начал reply (r), потом отменил (Esc), затем отправил
// Это эмулируется отправкой без reply_to
client.send_message(123, "Regular message".to_string(), None);
// Проверяем что отправилось без reply_to
assert_eq!(client.sent_messages()[0].reply_to, None);
let messages = client.get_messages(123);
assert_eq!(messages[1].content, "Regular message");
}
/// Test: Forward создаёт сообщение с forward_from
#[test]
fn test_forward_creates_message_with_forward_from() {
let mut client = FakeTdClient::new();
// Создаём пересланное сообщение
let forwarded_msg = TestMessageBuilder::new("Forwarded text", 200)
.forwarded_from("Bob")
.build();
client = client.with_message(456, forwarded_msg);
// Проверяем что forward_from сохранено
let messages = client.get_messages(456);
assert_eq!(messages.len(), 1);
assert!(messages[0].forward_from.is_some());
let forward = messages[0].forward_from.as_ref().unwrap();
assert_eq!(forward.sender_name, "Bob");
assert!(forward.date > 0); // Дата установлена
}
/// Test: Forward показывает "↪ Переслано от ..."
/// Проверяем что у пересланного сообщения есть forward_from
#[test]
fn test_forward_displays_sender_name() {
let mut client = FakeTdClient::new();
let msg = TestMessageBuilder::new("Important info", 300)
.forwarded_from("Charlie")
.build();
client = client.with_message(789, msg);
let messages = client.get_messages(789);
let forward = messages[0].forward_from.as_ref().unwrap();
// В UI это будет отображаться как "↪ Переслано от Charlie"
assert_eq!(forward.sender_name, "Charlie");
}
/// Test: Forward в другой чат
#[test]
fn test_forward_to_different_chat() {
let mut client = FakeTdClient::new();
// Исходное сообщение в чате 123
let original = TestMessageBuilder::new("Share this", 100)
.sender("Alice")
.build();
client = client.with_message(123, original);
// Пересылаем в чат 456
let forwarded = TestMessageBuilder::new("Share this", 101)
.forwarded_from("Alice")
.build();
client = client.with_message(456, forwarded);
// Проверяем что в первом чате 1 сообщение
assert_eq!(client.get_messages(123).len(), 1);
// Проверяем что во втором чате тоже 1 сообщение (пересланное)
assert_eq!(client.get_messages(456).len(), 1);
assert!(client.get_messages(456)[0].forward_from.is_some());
}
/// Test: Reply + Forward комбинация (ответ на пересланное сообщение)
#[test]
fn test_reply_to_forwarded_message() {
let mut client = FakeTdClient::new();
// Пересланное сообщение
let forwarded = TestMessageBuilder::new("Forwarded", 100)
.forwarded_from("Bob")
.build();
client = client.with_message(123, forwarded);
// Отвечаем на пересланное сообщение
let reply_id = client.send_message(123, "Thanks for sharing!".to_string(), Some(100));
// Проверяем что reply содержит reply_to
assert_eq!(client.sent_messages()[0].reply_to, Some(100));
let messages = client.get_messages(123);
assert_eq!(messages.len(), 2);
assert_eq!(messages[1].id, reply_id);
}
/// Test: Forward множества сообщений (batch forward)
#[test]
fn test_forward_multiple_messages() {
let mut client = FakeTdClient::new();
// Создаём 3 пересланных сообщения
let msg1 = TestMessageBuilder::new("Message 1", 100)
.forwarded_from("Alice")
.build();
let msg2 = TestMessageBuilder::new("Message 2", 101)
.forwarded_from("Alice")
.build();
let msg3 = TestMessageBuilder::new("Message 3", 102)
.forwarded_from("Alice")
.build();
client = client
.with_message(456, msg1)
.with_message(456, msg2)
.with_message(456, msg3);
// Проверяем что все 3 сообщения пересланы
let messages = client.get_messages(456);
assert_eq!(messages.len(), 3);
assert!(messages[0].forward_from.is_some());
assert!(messages[1].forward_from.is_some());
assert!(messages[2].forward_from.is_some());
}

119
tests/screens.rs Normal file
View File

@@ -0,0 +1,119 @@
// Screen snapshot tests
mod helpers;
use helpers::app_builder::TestAppBuilder;
use helpers::snapshot_utils::{render_to_buffer, buffer_to_string};
use helpers::test_data::create_test_chat;
use insta::assert_snapshot;
use tele_tui::app::AppScreen;
use tele_tui::tdlib::client::AuthState;
#[test]
fn snapshot_loading_screen_default() {
let mut app = TestAppBuilder::new()
.screen(AppScreen::Loading)
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::render(f, &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("loading_screen_default", output);
}
#[test]
fn snapshot_loading_screen_with_status() {
let mut app = TestAppBuilder::new()
.screen(AppScreen::Loading)
.status_message("Подключение к Telegram...")
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::render(f, &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("loading_screen_with_status", output);
}
#[test]
fn snapshot_auth_screen_phone() {
let mut app = TestAppBuilder::new()
.screen(AppScreen::Auth)
.auth_state(AuthState::WaitPhoneNumber)
.phone_input("+7")
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::render(f, &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("auth_screen_phone", output);
}
#[test]
fn snapshot_auth_screen_code() {
let mut app = TestAppBuilder::new()
.screen(AppScreen::Auth)
.auth_state(AuthState::WaitCode)
.code_input("1234")
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::render(f, &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("auth_screen_code", output);
}
#[test]
fn snapshot_auth_screen_password() {
let mut app = TestAppBuilder::new()
.screen(AppScreen::Auth)
.auth_state(AuthState::WaitPassword)
.password_input("pass")
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::render(f, &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("auth_screen_password", output);
}
#[test]
fn snapshot_main_screen_empty() {
let mut app = TestAppBuilder::new()
.screen(AppScreen::Main)
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::render(f, &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("main_screen_empty", output);
}
#[test]
fn snapshot_main_screen_terminal_too_small() {
let chat = create_test_chat("Mom", 123);
let mut app = TestAppBuilder::new()
.screen(AppScreen::Main)
.with_chat(chat)
.build();
// Use smaller terminal size (30x8) - below minimum 40x10
let buffer = render_to_buffer(30, 8, |f| {
tele_tui::ui::render(f, &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("main_screen_terminal_too_small", output);
}

241
tests/search.rs Normal file
View File

@@ -0,0 +1,241 @@
// Integration tests for search flow
mod helpers;
use helpers::fake_tdclient::FakeTdClient;
use helpers::test_data::{create_test_chat, TestChatBuilder, TestMessageBuilder};
/// Test: Поиск по чатам фильтрует по названию
#[test]
fn test_search_chats_by_title() {
let mut client = FakeTdClient::new();
let chat1 = create_test_chat("Mom", 123);
let chat2 = create_test_chat("Boss", 456);
let chat3 = create_test_chat("Mom's Work", 789);
client = client.with_chats(vec![chat1, chat2, chat3]);
// Ищем "mom" - должно найти "Mom" и "Mom's Work"
let query = "mom".to_lowercase();
let filtered: Vec<_> = client
.get_chats()
.iter()
.filter(|c| c.title.to_lowercase().contains(&query))
.collect();
assert_eq!(filtered.len(), 2);
assert_eq!(filtered[0].title, "Mom");
assert_eq!(filtered[1].title, "Mom's Work");
}
/// Test: Поиск по чатам фильтрует по @username
#[test]
fn test_search_chats_by_username() {
let mut client = FakeTdClient::new();
let chat1 = TestChatBuilder::new("Alice", 123)
.username("alice")
.build();
let chat2 = TestChatBuilder::new("Bob", 456)
.username("bobby")
.build();
let chat3 = TestChatBuilder::new("Charlie", 789).build(); // Без username
client = client.with_chats(vec![chat1, chat2, chat3]);
// Ищем "bob" - должно найти "Bob" (@bobby)
let query = "bob".to_lowercase();
let filtered: Vec<_> = client
.get_chats()
.iter()
.filter(|c| {
c.title.to_lowercase().contains(&query)
|| c.username
.as_ref()
.map(|u| u.to_lowercase().contains(&query))
.unwrap_or(false)
})
.collect();
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].title, "Bob");
}
/// Test: Пустой поисковый запрос возвращает все чаты
#[test]
fn test_search_empty_query_returns_all() {
let mut client = FakeTdClient::new();
let chat1 = create_test_chat("Mom", 123);
let chat2 = create_test_chat("Boss", 456);
let chat3 = create_test_chat("Friend", 789);
client = client.with_chats(vec![chat1, chat2, chat3]);
// Пустой запрос
let query = "";
let filtered: Vec<_> = client
.get_chats()
.iter()
.filter(|c| c.title.to_lowercase().contains(query))
.collect();
// Все чаты проходят фильтр (пустая строка содержится в любой строке)
assert_eq!(filtered.len(), 3);
}
/// Test: Поиск внутри чата по тексту сообщений
#[test]
fn test_search_messages_in_chat() {
let mut client = FakeTdClient::new();
let msg1 = TestMessageBuilder::new("Hello world", 100).build();
let msg2 = TestMessageBuilder::new("How are you?", 101).build();
let msg3 = TestMessageBuilder::new("Hello again", 102).build();
client = client.with_messages(123, vec![msg1, msg2, msg3]);
// Ищем "hello"
let query = "hello".to_lowercase();
let messages = client.get_messages(123);
let found: Vec<_> = messages
.iter()
.filter(|m| m.content.to_lowercase().contains(&query))
.collect();
assert_eq!(found.len(), 2);
assert_eq!(found[0].content, "Hello world");
assert_eq!(found[1].content, "Hello again");
}
/// Test: Навигация по результатам поиска (n/N)
#[test]
fn test_navigate_search_results() {
let mut client = FakeTdClient::new();
let msg1 = TestMessageBuilder::new("First match", 100).build();
let msg2 = TestMessageBuilder::new("Second match", 101).build();
let msg3 = TestMessageBuilder::new("Third match", 102).build();
client = client.with_messages(123, vec![msg1, msg2, msg3]);
// Ищем "match"
let query = "match".to_lowercase();
let messages = client.get_messages(123);
let results: Vec<_> = messages
.iter()
.enumerate()
.filter(|(_, m)| m.content.to_lowercase().contains(&query))
.collect();
assert_eq!(results.len(), 3);
// Навигация: начинаем с индекса 0
let mut current_index = 0;
// n - следующий результат
current_index = (current_index + 1) % results.len();
assert_eq!(current_index, 1);
assert_eq!(results[current_index].1.content, "Second match");
// n - ещё один
current_index = (current_index + 1) % results.len();
assert_eq!(current_index, 2);
assert_eq!(results[current_index].1.content, "Third match");
// n - wrap around к первому
current_index = (current_index + 1) % results.len();
assert_eq!(current_index, 0);
assert_eq!(results[current_index].1.content, "First match");
// N - предыдущий (wrap to last)
current_index = if current_index == 0 {
results.len() - 1
} else {
current_index - 1
};
assert_eq!(current_index, 2);
assert_eq!(results[current_index].1.content, "Third match");
}
/// Test: Поиск с учётом регистра (case-insensitive)
#[test]
fn test_search_case_insensitive() {
let mut client = FakeTdClient::new();
let msg1 = TestMessageBuilder::new("HELLO", 100).build();
let msg2 = TestMessageBuilder::new("hello", 101).build();
let msg3 = TestMessageBuilder::new("HeLLo", 102).build();
client = client.with_messages(123, vec![msg1, msg2, msg3]);
// Ищем "hello" (lowercase)
let query = "hello".to_lowercase();
let messages = client.get_messages(123);
let found: Vec<_> = messages
.iter()
.filter(|m| m.content.to_lowercase().contains(&query))
.collect();
// Все 3 варианта должны найтись
assert_eq!(found.len(), 3);
}
/// Test: Поиск не находит ничего
#[test]
fn test_search_no_results() {
let mut client = FakeTdClient::new();
let msg1 = TestMessageBuilder::new("Hello", 100).build();
let msg2 = TestMessageBuilder::new("World", 101).build();
client = client.with_messages(123, vec![msg1, msg2]);
// Ищем "xyz" - не должно найтись
let query = "xyz".to_lowercase();
let messages = client.get_messages(123);
let found: Vec<_> = messages
.iter()
.filter(|m| m.content.to_lowercase().contains(&query))
.collect();
assert_eq!(found.len(), 0);
}
/// Test: Отмена поиска (Esc) восстанавливает обычный режим
#[test]
fn test_cancel_search_restores_normal_mode() {
let mut client = FakeTdClient::new();
let chat1 = create_test_chat("Mom", 123);
let chat2 = create_test_chat("Boss", 456);
client = client.with_chats(vec![chat1, chat2]);
// Симулируем: пользователь начал поиск
let mut is_searching = true;
let mut search_query = "mom".to_string();
// Фильтруем
let query = search_query.to_lowercase();
let filtered: Vec<_> = client
.get_chats()
.iter()
.filter(|c| c.title.to_lowercase().contains(&query))
.collect();
assert_eq!(filtered.len(), 1);
// Пользователь нажал Esc
is_searching = false;
search_query.clear();
// После отмены видим все чаты
let all_chats = client.get_chats();
assert_eq!(all_chats.len(), 2);
assert!(!is_searching);
assert_eq!(search_query, "");
}

146
tests/send_message.rs Normal file
View File

@@ -0,0 +1,146 @@
// Integration tests for send message flow
mod helpers;
use helpers::fake_tdclient::FakeTdClient;
use helpers::test_data::{create_test_chat, TestMessageBuilder};
/// Test: Отправка текстового сообщения
#[test]
fn test_send_text_message() {
let mut client = FakeTdClient::new();
let chat = create_test_chat("Mom", 123);
client = client.with_chat(chat);
// Отправляем сообщение
let msg_id = client.send_message(123, "Hello, Mom!".to_string(), None);
// Проверяем что сообщение было отправлено
assert_eq!(client.sent_messages().len(), 1);
assert_eq!(client.sent_messages()[0].chat_id, 123);
assert_eq!(client.sent_messages()[0].text, "Hello, Mom!");
assert_eq!(client.sent_messages()[0].reply_to, None);
// Проверяем что сообщение добавилось в список
let messages = client.get_messages(123);
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].id, msg_id);
assert_eq!(messages[0].content, "Hello, Mom!");
assert_eq!(messages[0].is_outgoing, true);
}
/// Test: Отправка нескольких сообщений обновляет список
#[test]
fn test_send_multiple_messages_updates_list() {
let mut client = FakeTdClient::new();
// Отправляем первое сообщение
let msg1_id = client.send_message(123, "Message 1".to_string(), None);
// Отправляем второе сообщение
let msg2_id = client.send_message(123, "Message 2".to_string(), None);
// Отправляем третье сообщение
let msg3_id = client.send_message(123, "Message 3".to_string(), None);
// Проверяем что все 3 сообщения отслеживаются
assert_eq!(client.sent_messages().len(), 3);
// Проверяем что все сообщения в списке
let messages = client.get_messages(123);
assert_eq!(messages.len(), 3);
assert_eq!(messages[0].id, msg1_id);
assert_eq!(messages[1].id, msg2_id);
assert_eq!(messages[2].id, msg3_id);
assert_eq!(messages[0].content, "Message 1");
assert_eq!(messages[1].content, "Message 2");
assert_eq!(messages[2].content, "Message 3");
}
/// Test: Отправка пустого сообщения (должно быть игнорировано на уровне App)
/// Здесь мы тестируем что FakeTdClient технически может отправить пустое сообщение,
/// но в реальном App это должно фильтроваться
#[test]
fn test_send_empty_message_technical() {
let mut client = FakeTdClient::new();
// FakeTdClient технически может отправить пустое сообщение
let msg_id = client.send_message(123, "".to_string(), None);
// Проверяем что оно отправилось (в реальном App это должно фильтроваться)
assert_eq!(client.sent_messages().len(), 1);
assert_eq!(client.sent_messages()[0].text, "");
let messages = client.get_messages(123);
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].id, msg_id);
assert_eq!(messages[0].content, "");
}
/// Test: Отправка сообщения с форматированием (markdown сущности)
/// В данном случае мы не проверяем парсинг markdown, только что текст сохраняется
#[test]
fn test_send_message_with_markdown() {
let mut client = FakeTdClient::new();
let text = "**Bold** *italic* `code`";
client.send_message(123, text.to_string(), None);
// Проверяем что текст сохранился как есть (парсинг markdown - отдельная логика)
let messages = client.get_messages(123);
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].content, text);
}
/// Test: Отправка сообщения в разные чаты
#[test]
fn test_send_messages_to_different_chats() {
let mut client = FakeTdClient::new();
// Отправляем в чат 123
client.send_message(123, "Hello Mom".to_string(), None);
// Отправляем в чат 456
client.send_message(456, "Hello Boss".to_string(), None);
// Отправляем ещё одно в чат 123
client.send_message(123, "How are you?".to_string(), None);
// Проверяем общее количество отправленных
assert_eq!(client.sent_messages().len(), 3);
// Проверяем что сообщения распределены по чатам
let chat123_messages = client.get_messages(123);
assert_eq!(chat123_messages.len(), 2);
assert_eq!(chat123_messages[0].content, "Hello Mom");
assert_eq!(chat123_messages[1].content, "How are you?");
let chat456_messages = client.get_messages(456);
assert_eq!(chat456_messages.len(), 1);
assert_eq!(chat456_messages[0].content, "Hello Boss");
}
/// Test: Новое сообщение появляется в реальном времени (симуляция)
/// Тестируем что когда приходит новое входящее сообщение, оно добавляется в список
#[test]
fn test_receive_incoming_message() {
let mut client = FakeTdClient::new();
// Добавляем существующее сообщение
client.send_message(123, "My outgoing".to_string(), None);
// Симулируем входящее сообщение от собеседника
let incoming_msg = TestMessageBuilder::new("Hey there!", 2000)
.sender("Alice")
.build();
client = client.with_message(123, incoming_msg);
// Проверяем что в списке 2 сообщения
let messages = client.get_messages(123);
assert_eq!(messages.len(), 2);
assert_eq!(messages[0].is_outgoing, true); // Наше сообщение
assert_eq!(messages[1].is_outgoing, false); // Входящее
assert_eq!(messages[1].content, "Hey there!");
assert_eq!(messages[1].sender_name, "Alice");
}

View File

@@ -0,0 +1,5 @@
---
source: tests/footer.rs
expression: output
---
⏳ Подключение... | Инициализация TDLib...

View File

@@ -0,0 +1,5 @@
---
source: tests/footer.rs
expression: output
---
⏳ Подключение... | Инициализация TDLib...

View File

@@ -0,0 +1,5 @@
---
source: tests/footer.rs
expression: output
---
⏳ Прокси... | Инициализация TDLib...

View File

@@ -0,0 +1,5 @@
---
source: tests/footer.rs
expression: output
---
⚠ Нет сети | Инициализация TDLib...

View File

@@ -0,0 +1,5 @@
---
source: tests/footer.rs
expression: output
---
⏳ Подключение... | Инициализация TDLib...

View File

@@ -0,0 +1,5 @@
---
source: tests/footer.rs
expression: output
---
⏳ Подключение... | Инициализация TDLib...

View File

@@ -0,0 +1,28 @@
---
source: tests/input_field.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│Нет сообщений │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/input_field.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│ Вы ──────────────── │
│ Original message text (14:33 ✓✓) │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌ Редактирование (Esc отмена) ─────────────────────────────────────────────────┐
│✏ Edited text here │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/input_field.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│Нет сообщений │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> This is a longer message that will wrap to multiple lines in the input field│
│for testing purposes. │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/input_field.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│Нет сообщений │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod │
│tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, │
│quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo │
│consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse │
│cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non │
│proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed │
│ut perspiciatis unde omnis iste natus error sit voluptatem accusantium │
│doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/input_field.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│Mom ──────────────── │
│ (14:33) What do you think about this? │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌ Ответ (Esc отмена) ──────────────────────────────────────────────────────────┐
│↪ Mom: What do yo > I think it's great! │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/input_field.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│🔍 hello │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/input_field.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│Нет сообщений │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> Hello, how are you? │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,22 @@
---
source: tests/screens.rs
expression: output
---
┌──────────────────────────────────────┐
│ TTUI - Telegram Authentication │
└──────────────────────────────────────┘
Введите код подтверждения из Telegram
Код был отправлен на ваш номер
┌ Verification Code ───────────────────┐
│ 🔐 1234 │
└──────────────────────────────────────┘
Инициализация TDLib...

View File

@@ -0,0 +1,22 @@
---
source: tests/screens.rs
expression: output
---
┌──────────────────────────────────────┐
│ TTUI - Telegram Authentication │
└──────────────────────────────────────┘
Введите пароль двухфакторной аутентифика
┌ Password ────────────────────────────┐
│ 🔒 **** │
└──────────────────────────────────────┘
Инициализация TDLib...

View File

@@ -0,0 +1,22 @@
---
source: tests/screens.rs
expression: output
---
┌──────────────────────────────────────┐
│ TTUI - Telegram Authentication │
└──────────────────────────────────────┘
Введите номер телефона в международном ф
Пример: +79991111111
┌ Phone Number ────────────────────────┐
│ 📱 +7 │
└──────────────────────────────────────┘
Инициализация TDLib...

View File

@@ -0,0 +1,18 @@
---
source: tests/screens.rs
expression: output
---
┌ TTUI ────────────────────────────────────────────────────────────────────────┐
│ Инициализация TDLib... │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,18 @@
---
source: tests/screens.rs
expression: output
---
┌ TTUI ────────────────────────────────────────────────────────────────────────┐
│ Подключение к Telegram... │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/screens.rs
expression: output
---
┌ TTUI ────────────────────────────────────────────────────────────────────────┐
│ 1:All │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────┐┌──────────────────────────────────────────────────────┐
│🔍 Ctrl+S для поиска ││ Выберите чат │
└──────────────────────┘│ │
┌──────────────────────┐│ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
└──────────────────────┘│ │
┌──────────────────────┐│ │
│ ││ │
└──────────────────────┘└──────────────────────────────────────────────────────┘
⏳ Подключение... | Инициализация TDLib...

View File

@@ -0,0 +1,6 @@
---
source: tests/screens.rs
expression: output
---
30x8
Минимум: 40x10