This commit is contained in:
Mikhail Kilin
2026-01-28 11:39:21 +03:00
parent 051c4a0265
commit 68a2b7a982
56 changed files with 4424 additions and 5 deletions

171
tests/chat_list.rs Normal file
View File

@@ -0,0 +1,171 @@
// Chat list UI snapshot tests
mod helpers;
use helpers::test_data::{TestChatBuilder, 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_chat_list() {
let mut app = TestAppBuilder::new().build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::chat_list::render(f, f.area(), &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("empty_chat_list", output);
}
#[test]
fn snapshot_chat_list_with_three_chats() {
let chat1 = create_test_chat("Mom", 123);
let chat2 = create_test_chat("Boss", 456);
let chat3 = create_test_chat("Rust Community", 789);
let mut app = TestAppBuilder::new()
.with_chats(vec![chat1, chat2, chat3])
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::chat_list::render(f, f.area(), &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("chat_list_three_chats", output);
}
#[test]
fn snapshot_chat_with_unread_count() {
let chat = TestChatBuilder::new("Mom", 123)
.unread_count(5)
.last_message("Привет, как дела?")
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::chat_list::render(f, f.area(), &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("chat_with_unread", output);
}
#[test]
fn snapshot_chat_with_pinned() {
let chat = TestChatBuilder::new("Important Chat", 123)
.pinned()
.last_message("Pinned message")
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::chat_list::render(f, f.area(), &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("chat_pinned", output);
}
#[test]
fn snapshot_chat_with_muted() {
let chat = TestChatBuilder::new("Spam Group", 123)
.muted()
.unread_count(99)
.last_message("Too many messages")
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::chat_list::render(f, f.area(), &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("chat_muted", output);
}
#[test]
fn snapshot_chat_with_mentions() {
let chat = TestChatBuilder::new("Work Group", 123)
.unread_count(10)
.unread_mentions(2)
.last_message("@me check this out")
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::chat_list::render(f, f.area(), &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("chat_with_mentions", output);
}
#[test]
fn snapshot_selected_chat() {
let chat1 = create_test_chat("Mom", 123);
let chat2 = create_test_chat("Boss", 456);
let mut app = TestAppBuilder::new()
.with_chats(vec![chat1, chat2])
.selected_chat(123)
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::chat_list::render(f, f.area(), &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("chat_selected", output);
}
#[test]
fn snapshot_chat_long_title() {
let chat = TestChatBuilder::new("Very Long Chat Title That Should Be Truncated", 123)
.last_message("Test message")
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::chat_list::render(f, f.area(), &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("chat_long_title", output);
}
#[test]
fn snapshot_chat_search_mode() {
let chat1 = create_test_chat("Mom", 123);
let chat2 = create_test_chat("Boss", 456);
let chat3 = create_test_chat("Rust Community", 789);
let mut app = TestAppBuilder::new()
.with_chats(vec![chat1, chat2, chat3])
.searching("Mom")
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::chat_list::render(f, f.area(), &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("chat_list_search_mode", output);
}

View File

@@ -0,0 +1,277 @@
// Test App builder
use tele_tui::app::{App, AppScreen};
use tele_tui::config::Config;
use tele_tui::tdlib::{ChatInfo, MessageInfo};
use ratatui::widgets::ListState;
use std::collections::HashMap;
/// Builder для создания тестового App
///
/// Примечание: Так как App содержит реальный TdClient,
/// этот билдер подходит только для UI/snapshot тестов.
/// Для интеграционных тестов логики понадобится рефакторинг
/// с выделением trait для TdClient.
pub struct TestAppBuilder {
config: Config,
screen: AppScreen,
chats: Vec<ChatInfo>,
selected_chat_id: Option<i64>,
message_input: String,
is_searching: bool,
search_query: String,
editing_message_id: Option<i64>,
replying_to_message_id: Option<i64>,
is_reaction_picker_mode: bool,
is_profile_mode: bool,
confirm_delete_message_id: Option<i64>,
messages: HashMap<i64, Vec<MessageInfo>>,
selected_message_index: Option<usize>,
message_search_mode: bool,
message_search_query: String,
forwarding_message_id: Option<i64>,
is_selecting_forward_chat: bool,
}
impl Default for TestAppBuilder {
fn default() -> Self {
Self::new()
}
}
impl TestAppBuilder {
pub fn new() -> Self {
Self {
config: Config::default(),
screen: AppScreen::Main,
chats: vec![],
selected_chat_id: None,
message_input: String::new(),
is_searching: false,
search_query: String::new(),
editing_message_id: None,
replying_to_message_id: None,
is_reaction_picker_mode: false,
is_profile_mode: false,
confirm_delete_message_id: None,
messages: HashMap::new(),
selected_message_index: None,
message_search_mode: false,
message_search_query: String::new(),
forwarding_message_id: None,
is_selecting_forward_chat: false,
}
}
/// Установить экран
pub fn screen(mut self, screen: AppScreen) -> Self {
self.screen = screen;
self
}
/// Установить конфиг
pub fn config(mut self, config: Config) -> Self {
self.config = config;
self
}
/// Добавить чат
pub fn with_chat(mut self, chat: ChatInfo) -> Self {
self.chats.push(chat);
self
}
/// Добавить несколько чатов
pub fn with_chats(mut self, chats: Vec<ChatInfo>) -> Self {
self.chats.extend(chats);
self
}
/// Выбрать чат
pub fn selected_chat(mut self, chat_id: i64) -> Self {
self.selected_chat_id = Some(chat_id);
self
}
/// Установить текст в инпуте
pub fn message_input(mut self, text: &str) -> Self {
self.message_input = text.to_string();
self
}
/// Режим поиска
pub fn searching(mut self, query: &str) -> Self {
self.is_searching = true;
self.search_query = query.to_string();
self
}
/// Режим редактирования сообщения
pub fn editing_message(mut self, message_id: i64) -> Self {
self.editing_message_id = Some(message_id);
self
}
/// Режим ответа на сообщение
pub fn replying_to(mut self, message_id: i64) -> Self {
self.replying_to_message_id = Some(message_id);
self
}
/// Режим выбора реакции
pub fn reaction_picker(mut self) -> Self {
self.is_reaction_picker_mode = true;
self
}
/// Режим профиля
pub fn profile_mode(mut self) -> Self {
self.is_profile_mode = true;
self
}
/// Подтверждение удаления
pub fn delete_confirmation(mut self, message_id: i64) -> Self {
self.confirm_delete_message_id = Some(message_id);
self
}
/// Добавить сообщение для чата
pub fn with_message(mut self, chat_id: i64, message: MessageInfo) -> Self {
self.messages.entry(chat_id).or_insert_with(Vec::new).push(message);
self
}
/// Добавить несколько сообщений для чата
pub fn with_messages(mut self, chat_id: i64, messages: Vec<MessageInfo>) -> Self {
self.messages.entry(chat_id).or_insert_with(Vec::new).extend(messages);
self
}
/// Установить выбранное сообщение (режим selection)
pub fn selecting_message(mut self, message_index: usize) -> Self {
self.selected_message_index = Some(message_index);
self
}
/// Режим поиска по сообщениям в чате
pub fn message_search(mut self, query: &str) -> Self {
self.message_search_mode = true;
self.message_search_query = query.to_string();
self
}
/// Режим пересылки сообщения
pub fn forward_mode(mut self, message_id: i64) -> Self {
self.forwarding_message_id = Some(message_id);
self.is_selecting_forward_chat = true;
self
}
/// Построить App
///
/// ВАЖНО: Этот метод создаёт App с реальным TdClient,
/// поэтому он подходит только для UI тестов, где мы
/// не вызываем методы TdClient.
pub fn build(self) -> App {
let mut app = App::new(self.config);
app.screen = self.screen;
app.chats = self.chats;
app.selected_chat_id = self.selected_chat_id;
app.message_input = self.message_input;
app.is_searching = self.is_searching;
app.search_query = self.search_query;
app.editing_message_id = self.editing_message_id;
app.replying_to_message_id = self.replying_to_message_id;
app.is_reaction_picker_mode = self.is_reaction_picker_mode;
app.is_profile_mode = self.is_profile_mode;
app.confirm_delete_message_id = self.confirm_delete_message_id;
app.selected_message_index = self.selected_message_index;
app.is_message_search_mode = self.message_search_mode;
app.message_search_query = self.message_search_query;
app.forwarding_message_id = self.forwarding_message_id;
app.is_selecting_forward_chat = self.is_selecting_forward_chat;
// Выбираем первый чат если есть
if !app.chats.is_empty() {
let mut list_state = ListState::default();
list_state.select(Some(0));
app.chat_list_state = list_state;
}
// Применяем сообщения к текущему открытому чату
if let Some(chat_id) = self.selected_chat_id {
if let Some(messages) = self.messages.get(&chat_id) {
app.td_client.current_chat_messages = messages.clone();
app.td_client.current_chat_id = Some(chat_id);
}
}
app
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::helpers::test_data::create_test_chat;
#[test]
fn test_builder_defaults() {
let app = TestAppBuilder::new().build();
assert_eq!(app.screen, AppScreen::Main);
assert_eq!(app.chats.len(), 0);
assert_eq!(app.selected_chat_id, None);
assert_eq!(app.message_input, "");
}
#[test]
fn test_builder_with_chats() {
let chat1 = create_test_chat("Mom", 123);
let chat2 = create_test_chat("Boss", 456);
let app = TestAppBuilder::new()
.with_chat(chat1)
.with_chat(chat2)
.build();
assert_eq!(app.chats.len(), 2);
assert_eq!(app.chats[0].title, "Mom");
assert_eq!(app.chats[1].title, "Boss");
}
#[test]
fn test_builder_with_selected_chat() {
let chat = create_test_chat("Mom", 123);
let app = TestAppBuilder::new()
.with_chat(chat)
.selected_chat(123)
.build();
assert_eq!(app.selected_chat_id, Some(123));
}
#[test]
fn test_builder_editing_mode() {
let app = TestAppBuilder::new()
.editing_message(999)
.message_input("Edited text")
.build();
assert_eq!(app.editing_message_id, Some(999));
assert_eq!(app.message_input, "Edited text");
}
#[test]
fn test_builder_search_mode() {
let app = TestAppBuilder::new()
.searching("test query")
.build();
assert!(app.is_searching);
assert_eq!(app.search_query, "test query");
}
}

View File

@@ -0,0 +1,280 @@
// Fake TDLib client for testing
use std::collections::HashMap;
use tele_tui::tdlib::{ChatInfo, MessageInfo, FolderInfo, NetworkState};
/// Упрощённый mock TDLib клиента для тестов
#[derive(Clone)]
pub struct FakeTdClient {
pub chats: Vec<ChatInfo>,
pub messages: HashMap<i64, Vec<MessageInfo>>,
pub folders: Vec<FolderInfo>,
pub user_names: HashMap<i64, String>,
pub network_state: NetworkState,
pub typing_chat_id: Option<i64>,
pub sent_messages: Vec<SentMessage>,
pub edited_messages: Vec<EditedMessage>,
pub deleted_messages: Vec<i64>,
pub reactions: HashMap<i64, Vec<String>>, // message_id -> emojis
}
#[derive(Debug, Clone)]
pub struct SentMessage {
pub chat_id: i64,
pub text: String,
pub reply_to: Option<i64>,
}
#[derive(Debug, Clone)]
pub struct EditedMessage {
pub message_id: i64,
pub new_text: String,
}
impl Default for FakeTdClient {
fn default() -> Self {
Self::new()
}
}
impl FakeTdClient {
pub fn new() -> Self {
Self {
chats: vec![],
messages: HashMap::new(),
folders: vec![
FolderInfo {
id: 0,
name: "All".to_string(),
},
],
user_names: HashMap::new(),
network_state: NetworkState::Ready,
typing_chat_id: None,
sent_messages: vec![],
edited_messages: vec![],
deleted_messages: vec![],
reactions: HashMap::new(),
}
}
/// Добавить чат
pub fn with_chat(mut self, chat: ChatInfo) -> Self {
self.chats.push(chat);
self
}
/// Добавить несколько чатов
pub fn with_chats(mut self, chats: Vec<ChatInfo>) -> Self {
self.chats.extend(chats);
self
}
/// Добавить сообщение в чат
pub fn with_message(mut self, chat_id: i64, message: MessageInfo) -> Self {
self.messages
.entry(chat_id)
.or_insert_with(Vec::new)
.push(message);
self
}
/// Добавить несколько сообщений в чат
pub fn with_messages(mut self, chat_id: i64, messages: Vec<MessageInfo>) -> Self {
self.messages
.entry(chat_id)
.or_insert_with(Vec::new)
.extend(messages);
self
}
/// Добавить папку
pub fn with_folder(mut self, id: i32, name: &str) -> Self {
self.folders.push(FolderInfo {
id,
name: name.to_string(),
});
self
}
/// Добавить пользователя
pub fn with_user(mut self, id: i64, name: &str) -> Self {
self.user_names.insert(id, name.to_string());
self
}
/// Установить состояние сети
pub fn with_network_state(mut self, state: NetworkState) -> Self {
self.network_state = state;
self
}
/// Получить чаты
pub fn get_chats(&self) -> &[ChatInfo] {
&self.chats
}
/// Получить сообщения для чата
pub fn get_messages(&self, chat_id: i64) -> Vec<MessageInfo> {
self.messages
.get(&chat_id)
.cloned()
.unwrap_or_default()
}
/// Получить папки
pub fn get_folders(&self) -> &[FolderInfo] {
&self.folders
}
/// Отправить сообщение (мок)
pub fn send_message(&mut self, chat_id: i64, text: String, reply_to: Option<i64>) -> i64 {
let message_id = (self.sent_messages.len() as i64) + 1000;
self.sent_messages.push(SentMessage {
chat_id,
text: text.clone(),
reply_to,
});
// Добавляем сообщение в список сообщений чата
let message = MessageInfo {
id: message_id,
sender_name: "You".to_string(),
is_outgoing: true,
content: text,
entities: vec![],
date: 1640000000,
edit_date: 0,
is_read: true,
can_be_edited: true,
can_be_deleted_only_for_self: true,
can_be_deleted_for_all_users: true,
reply_to: None,
forward_from: None,
reactions: vec![],
};
self.messages
.entry(chat_id)
.or_insert_with(Vec::new)
.push(message);
message_id
}
/// Редактировать сообщение (мок)
pub fn edit_message(&mut self, chat_id: i64, message_id: i64, new_text: String) {
self.edited_messages.push(EditedMessage {
message_id,
new_text: new_text.clone(),
});
// Обновляем сообщение в списке
if let Some(messages) = self.messages.get_mut(&chat_id) {
if let Some(msg) = messages.iter_mut().find(|m| m.id == message_id) {
msg.content = new_text;
msg.edit_date = msg.date + 60;
}
}
}
/// Удалить сообщение (мок)
pub fn delete_message(&mut self, chat_id: i64, message_id: i64) {
self.deleted_messages.push(message_id);
// Удаляем сообщение из списка
if let Some(messages) = self.messages.get_mut(&chat_id) {
messages.retain(|m| m.id != message_id);
}
}
/// Добавить реакцию (мок)
pub fn add_reaction(&mut self, message_id: i64, emoji: String) {
self.reactions
.entry(message_id)
.or_insert_with(Vec::new)
.push(emoji);
}
/// Установить статус "печатает"
pub fn set_typing(&mut self, chat_id: Option<i64>) {
self.typing_chat_id = chat_id;
}
/// Получить список отправленных сообщений
pub fn sent_messages(&self) -> &[SentMessage] {
&self.sent_messages
}
/// Получить список отредактированных сообщений
pub fn edited_messages(&self) -> &[EditedMessage] {
&self.edited_messages
}
/// Получить список удалённых сообщений
pub fn deleted_messages(&self) -> &[i64] {
&self.deleted_messages
}
/// Очистить историю действий
pub fn clear_history(&mut self) {
self.sent_messages.clear();
self.edited_messages.clear();
self.deleted_messages.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::helpers::test_data::create_test_chat;
#[test]
fn test_fake_client_creation() {
let client = FakeTdClient::new();
assert_eq!(client.chats.len(), 0);
assert_eq!(client.folders.len(), 1); // Default "All" folder
}
#[test]
fn test_fake_client_with_chat() {
let chat = create_test_chat("Mom", 123);
let client = FakeTdClient::new().with_chat(chat);
assert_eq!(client.chats.len(), 1);
assert_eq!(client.chats[0].title, "Mom");
}
#[test]
fn test_send_message() {
let mut client = FakeTdClient::new();
let msg_id = client.send_message(123, "Hello".to_string(), None);
assert_eq!(client.sent_messages().len(), 1);
assert_eq!(client.sent_messages()[0].text, "Hello");
assert_eq!(client.get_messages(123).len(), 1);
assert_eq!(client.get_messages(123)[0].id, msg_id);
}
#[test]
fn test_edit_message() {
let mut client = FakeTdClient::new();
let msg_id = client.send_message(123, "Hello".to_string(), None);
client.edit_message(123, msg_id, "Hello World".to_string());
assert_eq!(client.edited_messages().len(), 1);
assert_eq!(client.get_messages(123)[0].content, "Hello World");
assert!(client.get_messages(123)[0].edit_date > 0);
}
#[test]
fn test_delete_message() {
let mut client = FakeTdClient::new();
let msg_id = client.send_message(123, "Hello".to_string(), None);
client.delete_message(123, msg_id);
assert_eq!(client.deleted_messages().len(), 1);
assert_eq!(client.get_messages(123).len(), 0);
}
}

11
tests/helpers/mod.rs Normal file
View File

@@ -0,0 +1,11 @@
// Test helpers module
pub mod app_builder;
pub mod fake_tdclient;
pub mod snapshot_utils;
pub mod test_data;
pub use app_builder::TestAppBuilder;
pub use fake_tdclient::FakeTdClient;
pub use snapshot_utils::{buffer_to_string, render_to_buffer};
pub use test_data::{create_test_chat, create_test_message, create_test_user};

View File

@@ -0,0 +1,89 @@
// Snapshot testing utilities
use ratatui::backend::TestBackend;
use ratatui::Terminal;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
/// Конвертирует Buffer в читаемую строку для snapshot тестов
pub fn buffer_to_string(buffer: &Buffer) -> String {
let area = buffer.area();
let mut result = String::new();
for y in 0..area.height {
let mut line = String::new();
for x in 0..area.width {
line.push_str(buffer[(x, y)].symbol());
}
// Убираем trailing spaces в конце строки
result.push_str(line.trim_end());
if y < area.height - 1 {
result.push('\n');
}
}
result
}
/// Создаёт TestBackend с заданным размером и рендерит UI
pub fn render_to_buffer<F>(width: u16, height: u16, render_fn: F) -> Buffer
where
F: FnOnce(&mut ratatui::Frame),
{
let backend = TestBackend::new(width, height);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(render_fn)
.unwrap();
terminal.backend().buffer().clone()
}
/// Макрос для упрощения snapshot тестов
#[macro_export]
macro_rules! assert_ui_snapshot {
($name:expr, $width:expr, $height:expr, $render_fn:expr) => {{
use $crate::helpers::snapshot_utils::{render_to_buffer, buffer_to_string};
let buffer = render_to_buffer($width, $height, $render_fn);
let output = buffer_to_string(&buffer);
insta::assert_snapshot!($name, output);
}};
}
#[cfg(test)]
mod tests {
use super::*;
use ratatui::widgets::{Block, Borders};
#[test]
fn test_buffer_to_string_simple() {
let buffer = render_to_buffer(10, 3, |f| {
let block = Block::default()
.borders(Borders::ALL)
.title("Hi");
f.render_widget(block, f.area());
});
let result = buffer_to_string(&buffer);
assert!(result.contains("Hi"));
assert!(result.contains(""));
assert!(result.contains(""));
}
#[test]
fn test_buffer_to_string_removes_trailing_spaces() {
let buffer = render_to_buffer(20, 3, |f| {
let block = Block::default().title("Test");
f.render_widget(block, Rect::new(0, 0, 10, 3));
});
let result = buffer_to_string(&buffer);
let lines: Vec<&str> = result.lines().collect();
// Проверяем что trailing spaces убраны
for line in lines {
assert!(!line.ends_with(' ') || line.trim().is_empty());
}
}
}

241
tests/helpers/test_data.rs Normal file
View File

@@ -0,0 +1,241 @@
// Test data builders and fixtures
use tele_tui::tdlib::{ChatInfo, MessageInfo, ReactionInfo, ReplyInfo, ForwardInfo, ProfileInfo};
/// Builder для создания тестового чата
pub struct TestChatBuilder {
id: i64,
title: String,
username: Option<String>,
last_message: String,
last_message_date: i32,
unread_count: i32,
unread_mention_count: i32,
is_pinned: bool,
order: i64,
last_read_outbox_message_id: i64,
folder_ids: Vec<i32>,
is_muted: bool,
draft_text: Option<String>,
}
impl TestChatBuilder {
pub fn new(title: &str, id: i64) -> Self {
Self {
id,
title: title.to_string(),
username: None,
last_message: "".to_string(),
last_message_date: 1640000000,
unread_count: 0,
unread_mention_count: 0,
is_pinned: false,
order: id,
last_read_outbox_message_id: 0,
folder_ids: vec![0],
is_muted: false,
draft_text: None,
}
}
pub fn username(mut self, username: &str) -> Self {
self.username = Some(username.to_string());
self
}
pub fn last_message(mut self, text: &str) -> Self {
self.last_message = text.to_string();
self
}
pub fn unread_count(mut self, count: i32) -> Self {
self.unread_count = count;
self
}
pub fn unread_mentions(mut self, count: i32) -> Self {
self.unread_mention_count = count;
self
}
pub fn pinned(mut self) -> Self {
self.is_pinned = true;
self
}
pub fn muted(mut self) -> Self {
self.is_muted = true;
self
}
pub fn draft(mut self, text: &str) -> Self {
self.draft_text = Some(text.to_string());
self
}
pub fn folder(mut self, folder_id: i32) -> Self {
self.folder_ids = vec![folder_id];
self
}
pub fn build(self) -> ChatInfo {
ChatInfo {
id: self.id,
title: self.title,
username: self.username,
last_message: self.last_message,
last_message_date: self.last_message_date,
unread_count: self.unread_count,
unread_mention_count: self.unread_mention_count,
is_pinned: self.is_pinned,
order: self.order,
last_read_outbox_message_id: self.last_read_outbox_message_id,
folder_ids: self.folder_ids,
is_muted: self.is_muted,
draft_text: self.draft_text,
}
}
}
/// Builder для создания тестового сообщения
pub struct TestMessageBuilder {
id: i64,
sender_name: String,
is_outgoing: bool,
content: String,
entities: Vec<tdlib_rs::types::TextEntity>,
date: i32,
edit_date: i32,
is_read: bool,
can_be_edited: bool,
can_be_deleted_only_for_self: bool,
can_be_deleted_for_all_users: bool,
reply_to: Option<ReplyInfo>,
forward_from: Option<ForwardInfo>,
reactions: Vec<ReactionInfo>,
}
impl TestMessageBuilder {
pub fn new(content: &str, id: i64) -> Self {
Self {
id,
sender_name: "User".to_string(),
is_outgoing: false,
content: content.to_string(),
entities: vec![],
date: 1640000000,
edit_date: 0,
is_read: true,
can_be_edited: false,
can_be_deleted_only_for_self: true,
can_be_deleted_for_all_users: false,
reply_to: None,
forward_from: None,
reactions: vec![],
}
}
pub fn outgoing(mut self) -> Self {
self.is_outgoing = true;
self.sender_name = "You".to_string();
self.can_be_edited = true;
self.can_be_deleted_for_all_users = true;
self
}
pub fn sender(mut self, name: &str) -> Self {
self.sender_name = name.to_string();
self
}
pub fn date(mut self, timestamp: i32) -> Self {
self.date = timestamp;
self
}
pub fn edited(mut self) -> Self {
self.edit_date = self.date + 60;
self
}
pub fn unread(mut self) -> Self {
self.is_read = false;
self
}
pub fn reply_to(mut self, message_id: i64, sender: &str, text: &str) -> Self {
self.reply_to = Some(ReplyInfo {
message_id,
sender_name: sender.to_string(),
text: text.to_string(),
});
self
}
pub fn forwarded_from(mut self, sender: &str) -> Self {
self.forward_from = Some(ForwardInfo {
sender_name: sender.to_string(),
date: self.date - 3600,
});
self
}
pub fn reaction(mut self, emoji: &str, count: i32, chosen: bool) -> Self {
self.reactions.push(ReactionInfo {
emoji: emoji.to_string(),
count,
is_chosen: chosen,
});
self
}
pub fn build(self) -> MessageInfo {
MessageInfo {
id: self.id,
sender_name: self.sender_name,
is_outgoing: self.is_outgoing,
content: self.content,
entities: self.entities,
date: self.date,
edit_date: self.edit_date,
is_read: self.is_read,
can_be_edited: self.can_be_edited,
can_be_deleted_only_for_self: self.can_be_deleted_only_for_self,
can_be_deleted_for_all_users: self.can_be_deleted_for_all_users,
reply_to: self.reply_to,
forward_from: self.forward_from,
reactions: self.reactions,
}
}
}
/// Хелперы для быстрого создания тестовых данных
pub fn create_test_chat(title: &str, id: i64) -> ChatInfo {
TestChatBuilder::new(title, id).build()
}
pub fn create_test_message(content: &str, id: i64) -> MessageInfo {
TestMessageBuilder::new(content, id).build()
}
pub fn create_test_user(name: &str, id: i64) -> (i64, String) {
(id, name.to_string())
}
/// Хелпер для создания профиля
pub fn create_test_profile(title: &str, chat_id: i64) -> ProfileInfo {
ProfileInfo {
chat_id,
title: title.to_string(),
username: None,
bio: None,
phone_number: None,
chat_type: "Личный чат".to_string(),
member_count: None,
description: None,
invite_link: None,
is_group: false,
online_status: None,
}
}

399
tests/messages.rs Normal file
View File

@@ -0,0 +1,399 @@
// Messages UI snapshot tests
mod helpers;
use helpers::test_data::{TestChatBuilder, 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_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::messages::render(f, f.area(), &app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("empty_chat", output);
}
#[test]
fn snapshot_single_incoming_message() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("Hello there!", 1)
.sender("Mom")
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.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!("single_incoming_message", output);
}
#[test]
fn snapshot_single_outgoing_message() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("Hi mom!", 1)
.outgoing()
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.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!("single_outgoing_message", output);
}
#[test]
fn snapshot_date_separator_old_date() {
let chat = create_test_chat("Mom", 123);
// Use a fixed old date (20 Dec 2021) - will show as date separator
let message = TestMessageBuilder::new("Message from the past", 1)
.date(1640000000) // 20 Dec 2021
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.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!("date_separator_old_date", output);
}
// NOTE: Tests for "Сегодня" and "Вчера" date separators are skipped
// because they depend on current date and cannot be stable snapshots.
// The date formatting logic is tested manually and through the old_date test above.
#[test]
fn snapshot_sender_grouping() {
let chat = create_test_chat("Group Chat", 123);
let msg1 = TestMessageBuilder::new("First message", 1)
.sender("Alice")
.build();
let msg2 = TestMessageBuilder::new("Second message", 2)
.sender("Alice")
.build();
let msg3 = TestMessageBuilder::new("Third message", 3)
.sender("Bob")
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_messages(123, vec![msg1, msg2, msg3])
.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!("sender_grouping", output);
}
#[test]
fn snapshot_outgoing_sent() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("Just sent", 1)
.outgoing()
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.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!("outgoing_sent", output);
}
#[test]
fn snapshot_outgoing_read() {
let chat = TestChatBuilder::new("Mom", 123)
.last_message("Read message")
.build();
// Message with id < last_read_outbox_message_id means it's been read
let message = TestMessageBuilder::new("Read message", 1)
.outgoing()
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.selected_chat(123)
.build();
// Set last_read_outbox to simulate message being read
if let Some(chat) = app.chats.iter_mut().find(|c| c.id == 123) {
chat.last_read_outbox_message_id = 2;
}
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!("outgoing_read", output);
}
#[test]
fn snapshot_edited_message() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("Edited text", 1)
.edited()
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.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!("edited_message", output);
}
#[test]
fn snapshot_long_message_wrap() {
let chat = create_test_chat("Mom", 123);
let long_text = "This is a very long message that should wrap across multiple lines when rendered in the terminal UI. Let's make it even longer to ensure we test the wrapping behavior properly.";
let message = TestMessageBuilder::new(long_text, 1)
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.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!("long_message_wrap", output);
}
#[test]
fn snapshot_markdown_bold_italic_code() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("**bold** *italic* `code`", 1)
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.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!("markdown_bold_italic_code", output);
}
#[test]
fn snapshot_markdown_link_mention() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("Check [this](https://example.com) and @username", 1)
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.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!("markdown_link_mention", output);
}
#[test]
fn snapshot_markdown_spoiler() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("Spoiler: ||hidden text||", 1)
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.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!("markdown_spoiler", output);
}
#[test]
fn snapshot_media_placeholder() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("[Фото]", 1)
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.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!("media_placeholder", output);
}
#[test]
fn snapshot_reply_message() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("This is a reply", 2)
.reply_to(1, "Mom", "Original message text")
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.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!("reply_message", output);
}
#[test]
fn snapshot_forwarded_message() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("Forwarded content", 1)
.forwarded_from("Alice")
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.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!("forwarded_message", output);
}
#[test]
fn snapshot_single_reaction() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("Great!", 1)
.reaction("👍", 1, true)
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.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!("single_reaction", output);
}
#[test]
fn snapshot_multiple_reactions() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("Popular message", 1)
.reaction("👍", 5, true)
.reaction("👎", 3, false)
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.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!("multiple_reactions", output);
}
#[test]
fn snapshot_selected_message() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("Selected message", 1)
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.selected_chat(123)
.selecting_message(1)
.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!("selected_message", output);
}

202
tests/modals.rs Normal file
View File

@@ -0,0 +1,202 @@
// Modals UI snapshot tests
mod helpers;
use helpers::test_data::{TestChatBuilder, TestMessageBuilder, create_test_chat, create_test_profile};
use helpers::app_builder::TestAppBuilder;
use helpers::snapshot_utils::{render_to_buffer, buffer_to_string};
use insta::assert_snapshot;
#[test]
fn snapshot_delete_confirmation_modal() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("Delete me", 1)
.outgoing()
.build();
let app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.selected_chat(123)
.delete_confirmation(1)
.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!("delete_confirmation_modal", output);
}
#[test]
fn snapshot_emoji_picker_default() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("React to this", 1)
.build();
let app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.selected_chat(123)
.reaction_picker()
.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!("emoji_picker_default", output);
}
#[test]
fn snapshot_emoji_picker_with_selection() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("React to this", 1)
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.selected_chat(123)
.reaction_picker()
.build();
// Выбираем 5-ю реакцию (индекс 4)
app.selected_reaction_index = 4;
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!("emoji_picker_with_selection", output);
}
#[test]
fn snapshot_profile_personal_chat() {
let chat = create_test_chat("Alice", 123);
let profile = create_test_profile("Alice", 123);
let mut app = TestAppBuilder::new()
.with_chat(chat)
.selected_chat(123)
.profile_mode()
.build();
app.profile_info = Some(profile);
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!("profile_personal_chat", output);
}
#[test]
fn snapshot_profile_group_chat() {
let chat = TestChatBuilder::new("Work Group", 456)
.build();
let mut profile = create_test_profile("Work Group", 456);
profile.is_group = true;
profile.chat_type = "Группа".to_string();
profile.member_count = Some(25);
profile.description = Some("Work discussion group".to_string());
let mut app = TestAppBuilder::new()
.with_chat(chat)
.selected_chat(456)
.profile_mode()
.build();
app.profile_info = Some(profile);
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!("profile_group_chat", output);
}
#[test]
fn snapshot_pinned_message() {
let chat = create_test_chat("Mom", 123);
let message1 = TestMessageBuilder::new("Regular message", 1)
.build();
let pinned_msg = TestMessageBuilder::new("Important pinned message!", 2)
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_messages(123, vec![message1])
.selected_chat(123)
.build();
// Устанавливаем закреплённое сообщение
app.td_client.current_pinned_message = Some(pinned_msg);
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!("pinned_message", output);
}
#[test]
fn snapshot_search_in_chat() {
let chat = create_test_chat("Mom", 123);
let msg1 = TestMessageBuilder::new("Hello world", 1)
.build();
let msg2 = TestMessageBuilder::new("World is beautiful", 2)
.build();
let msg3 = TestMessageBuilder::new("Beautiful day", 3)
.build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_messages(123, vec![msg1.clone(), msg2.clone(), msg3])
.selected_chat(123)
.message_search("world")
.build();
// Устанавливаем результаты поиска
app.message_search_results = vec![msg1, msg2];
app.selected_search_result_index = 0;
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!("search_in_chat", output);
}
#[test]
fn snapshot_forward_mode() {
let chat1 = create_test_chat("Mom", 123);
let chat2 = create_test_chat("Dad", 456);
let chat3 = create_test_chat("Work Group", 789);
let message = TestMessageBuilder::new("Forward this message", 1)
.build();
let mut app = TestAppBuilder::new()
.with_chats(vec![chat1.clone(), chat2, chat3])
.with_message(123, message)
.selected_chat(123)
.forward_mode(1)
.build();
let buffer = render_to_buffer(80, 24, |f| {
// В forward mode показывается chat_list для выбора чата
tele_tui::ui::chat_list::render(f, f.area(), &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("forward_mode", output);
}

View File

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

View File

@@ -0,0 +1,28 @@
---
source: tests/chat_list.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│🔍 Ctrl+S для поиска │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ Mom │
│ Boss │
│ Rust Community │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/chat_list.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│🔍 Ctrl+S для поиска │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ Very Long Chat Title That Should Be Truncated │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/chat_list.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│🔍 Ctrl+S для поиска │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ 🔇 Spam Group (99) │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/chat_list.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│🔍 Ctrl+S для поиска │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ 📌 Important Chat │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/chat_list.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│🔍 Ctrl+S для поиска │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│▌ Mom │
│ Boss │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/chat_list.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│🔍 Ctrl+S для поиска │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ Work Group @ (10) │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/chat_list.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│🔍 Ctrl+S для поиска │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ Mom (5) │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/chat_list.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│🔍 Ctrl+S для поиска │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/messages.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│User ──────────────── │
│ (14:33) Message from the past │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/messages.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│User ──────────────── │
│ (14:33 ✎) Edited text │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

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

View File

@@ -0,0 +1,28 @@
---
source: tests/messages.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│User ──────────────── │
│↪ Переслано от Alice │
│ (14:33) Forwarded content │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/messages.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│User ──────────────── │
│ (14:33) This is a very long message that should wrap across multiple lines │
│ when rendered in the terminal UI. Let's make it even longer to │
│ ensure we test the wrapping behavior properly. │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/messages.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│User ──────────────── │
│ (14:33) **bold** *italic* `code` │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/messages.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│User ──────────────── │
│ (14:33) Check [this](https://example.com) and @username │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/messages.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│User ──────────────── │
│ (14:33) Spoiler: ||hidden text|| │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/messages.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│User ──────────────── │
│ (14:33) [Фото] │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/messages.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│User ──────────────── │
│ (14:33) Popular message │
│[👍 ] 5 👎 3 │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/messages.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│ Вы ──────────────── │
│ Read message (14:33 ✓✓) │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/messages.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│ Вы ──────────────── │
│ Just sent (14:33 ✓✓) │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/messages.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│User ──────────────── │
│┌ Mom: Original message text │
│ (14:33) This is a reply │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/messages.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│User ──────────────── │
│ (14:33) Selected message │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌ Выбор сообщения ─────────────────────────────────────────────────────────────┐
│↑↓ · r ответить · f переслать · y копировать · Esc │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/messages.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Group Chat │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│Alice ──────────────── │
│ (14:33) First message │
│ (14:33) Second message │
│ │
│Bob ──────────────── │
│ (14:33) Third message │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/messages.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│Mom ──────────────── │
│ (14:33) Hello there! │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/messages.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│ Вы ──────────────── │
│ Hi mom! (14:33 ✓✓) │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/messages.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│User ──────────────── │
│ (14:33) Great! │
│[👍 ] │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/modals.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│ Вы ──────────────── │
│ Delete me (14:33 ✓✓) │
│ ┌ Подтверждение ───────────────────────┐ │
│ │ │ │
│ │ Удалить сообщение? │ │
│ │ │ │
│ │ [y/Enter] Да [n/Esc] Нет │ │
│ │ │ │
│ └──────────────────────────────────────┘ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/modals.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│User ──────────────── │
│ (14:33) React to this │
│ │
│ │
│ ┌ Выбери реакцию ────────────────────────────────┐ │
│ │ │ │
│ │ │ │
│ └────────────────────────────────────────────────┘ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/modals.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│User ──────────────── │
│ (14:33) React to this │
│ │
│ │
│ ┌ Выбери реакцию ────────────────────────────────┐ │
│ │ │ │
│ │ │ │
│ └────────────────────────────────────────────────┘ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/modals.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│🔍 Ctrl+S для поиска │
└──────────────────────────────────────────────────────────────────────────────┘
┌ ↪ Выберите чат ──────────────────────────────────────────────────────────────┐
│▌ Mom │
│ Dad │
│ Work Group │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/modals.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 Mom │
└──────────────────────────────────────────────────────────────────────────────┘
📌 02.01.2022 14:33 Important pinned message! Ctrl+P
┌──────────────────────────────────────────────────────────────────────────────┐
│ ──────── 02.01.2022 ──────── │
│ │
│User ──────────────── │
│ (14:33) Regular message │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│> █ Введите сообщение... │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/modals.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 ПРОФИЛЬ: Work Group │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│Тип: Группа │
│ │
│ID: 456 │
│ │
│Участников: 25 │
│ │
│Описание: │
│Work discussion group │
│ │
│──────────────────────────────── │
│ │
│Действия: │
│ │
│▶ Скопировать ID │
│ Покинуть группу │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ↑↓ навигация Enter выбрать Esc выход │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/modals.rs
expression: output
---
┌──────────────────────────────────────────────────────────────────────────────┐
│👤 ПРОФИЛЬ: Alice │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│Тип: Личный чат │
│ │
│ID: 123 │
│ │
│──────────────────────────────── │
│ │
│Действия: │
│ │
│▶ Скопировать ID │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ↑↓ навигация Enter выбрать Esc выход │
└──────────────────────────────────────────────────────────────────────────────┘

View File

@@ -0,0 +1,28 @@
---
source: tests/modals.rs
expression: output
---
┌ Поиск по сообщениям ─────────────────────────────────────────────────────────┐
│🔍 world█ (1/2) │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│▶ User (02.01.2022 14:33) │
│ Hello world │
│ │
│ User (02.01.2022 14:33) │
│ World is beautiful │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ↑↓ навигация n/N след./пред. Enter перейти Esc выход │
└──────────────────────────────────────────────────────────────────────────────┘