refactor: restructure MessageInfo with logical field grouping (P2.6)

Сгруппированы 16 плоских полей MessageInfo в 4 логические структуры
для улучшения организации кода и maintainability.

Новые структуры:
- MessageMetadata: id, sender_name, date, edit_date
- MessageContent: text, entities
- MessageState: is_outgoing, is_read, can_be_edited, can_be_deleted_*
- MessageInteractions: reply_to, forward_from, reactions

Изменения:
- Добавлены 4 новые структуры в tdlib/types.rs
- Обновлена MessageInfo для использования новых структур
- Добавлен конструктор MessageInfo::new() для удобного создания
- Добавлены getter методы (id(), text(), sender_name() и др.) для удобного доступа
- Обновлены все места создания MessageInfo (convert_message)
- Обновлены все места использования (~200+ обращений):
  * ui/messages.rs: рендеринг сообщений
  * app/mod.rs: логика приложения
  * input/main_input.rs: обработка ввода и копирование
  * tdlib/client.rs: обработка updates
  * Все тестовые файлы (14 файлов)

Преимущества:
- Логическая группировка данных
- Проще понимать структуру сообщения
- Легче добавлять новые поля в будущем
- Улучшенная читаемость кода

Статус: Priority 2 теперь 80% (4/5 задач)
-  Error enum
-  Config validation
-  Newtype для ID
-  MessageInfo реструктуризация
-  MessageBuilder pattern

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Kilin
2026-01-31 01:45:54 +03:00
parent 7081a886ad
commit 43960332d9
14 changed files with 274 additions and 144 deletions

View File

@@ -188,8 +188,8 @@ impl App {
// Сначала извлекаем данные из сообщения // Сначала извлекаем данные из сообщения
let msg_data = self.get_selected_message().and_then(|msg| { let msg_data = self.get_selected_message().and_then(|msg| {
if msg.can_be_edited && msg.is_outgoing { if msg.can_be_edited() && msg.is_outgoing() {
Some((msg.id, msg.content.clone(), selected_idx.unwrap())) Some((msg.id()(), msg.text().to_string(), selected_idx.unwrap()))
} else { } else {
None None
} }
@@ -328,7 +328,7 @@ impl App {
pub fn start_reply_to_selected(&mut self) -> bool { pub fn start_reply_to_selected(&mut self) -> bool {
if let Some(msg) = self.get_selected_message() { if let Some(msg) = self.get_selected_message() {
self.chat_state = ChatState::Reply { self.chat_state = ChatState::Reply {
message_id: msg.id, message_id: msg.id(),
}; };
return true; return true;
} }
@@ -351,7 +351,7 @@ impl App {
self.td_client self.td_client
.current_chat_messages() .current_chat_messages()
.iter() .iter()
.find(|m| m.id == id) .find(|m| m.id() == id)
}) })
} }
@@ -359,7 +359,7 @@ impl App {
pub fn start_forward_selected(&mut self) -> bool { pub fn start_forward_selected(&mut self) -> bool {
if let Some(msg) = self.get_selected_message() { if let Some(msg) = self.get_selected_message() {
self.chat_state = ChatState::Forward { self.chat_state = ChatState::Forward {
message_id: msg.id, message_id: msg.id(),
selecting_chat: true, selecting_chat: true,
}; };
// Сбрасываем выбор чата на первый // Сбрасываем выбор чата на первый
@@ -388,7 +388,7 @@ impl App {
self.td_client self.td_client
.current_chat_messages() .current_chat_messages()
.iter() .iter()
.find(|m| m.id == id) .find(|m| m.id() == id)
}) })
} }
@@ -451,7 +451,7 @@ impl App {
/// Получить ID текущего pinned для перехода в историю /// Получить ID текущего pinned для перехода в историю
pub fn get_selected_pinned_id(&self) -> Option<i64> { pub fn get_selected_pinned_id(&self) -> Option<i64> {
self.get_selected_pinned().map(|m| m.id) self.get_selected_pinned().map(|m| m.id())
} }
// === Message Search Mode === // === Message Search Mode ===
@@ -522,7 +522,7 @@ impl App {
/// Получить ID выбранного результата для перехода /// Получить ID выбранного результата для перехода
pub fn get_selected_search_result_id(&self) -> Option<i64> { pub fn get_selected_search_result_id(&self) -> Option<i64> {
self.get_selected_search_result().map(|m| m.id) self.get_selected_search_result().map(|m| m.id())
} }
/// Получить поисковый запрос из режима поиска /// Получить поисковый запрос из режима поиска

View File

@@ -193,7 +193,7 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
.td_client .td_client
.current_chat_messages() .current_chat_messages()
.iter() .iter()
.position(|m| m.id == msg_id); .position(|m| m.id() == msg_id);
if let Some(idx) = msg_index { if let Some(idx) = msg_index {
let total = app.td_client.current_chat_messages().len(); let total = app.td_client.current_chat_messages().len();
@@ -268,7 +268,7 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
.td_client .td_client
.current_chat_messages() .current_chat_messages()
.iter() .iter()
.position(|m| m.id == msg_id); .position(|m| m.id() == msg_id);
if let Some(idx) = msg_index { if let Some(idx) = msg_index {
// Вычисляем scroll offset чтобы показать сообщение // Вычисляем scroll offset чтобы показать сообщение
@@ -381,8 +381,8 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
.td_client .td_client
.current_chat_messages() .current_chat_messages()
.iter() .iter()
.find(|m| m.id == msg_id) .find(|m| m.id() == msg_id)
.map(|m| m.can_be_deleted_for_all_users) .map(|m| m.can_be_deleted_for_all_users())
.unwrap_or(false); .unwrap_or(false);
match timeout( match timeout(
@@ -399,7 +399,7 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
// Удаляем из локального списка // Удаляем из локального списка
app.td_client app.td_client
.current_chat_messages_mut() .current_chat_messages_mut()
.retain(|m| m.id != msg_id); .retain(|m| m.id() != msg_id);
// Сбрасываем состояние // Сбрасываем состояние
app.chat_state = crate::app::ChatState::Normal; app.chat_state = crate::app::ChatState::Normal;
} }
@@ -582,11 +582,11 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
.td_client .td_client
.current_chat_messages_mut() .current_chat_messages_mut()
.iter_mut() .iter_mut()
.find(|m| m.id == msg_id) .find(|m| m.id() == msg_id)
{ {
msg.content = edited_msg.content; msg.content.text = edited_msg.content.text;
msg.entities = edited_msg.entities; msg.content.entities = edited_msg.content.entities;
msg.edit_date = edited_msg.edit_date; msg.metadata.edit_date = edited_msg.metadata.edit_date;
} }
} }
Ok(Err(e)) => { Ok(Err(e)) => {
@@ -607,9 +607,9 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
// Создаём ReplyInfo ДО отправки, пока сообщение точно доступно // Создаём ReplyInfo ДО отправки, пока сообщение точно доступно
let reply_info = app.get_replying_to_message().map(|m| { let reply_info = app.get_replying_to_message().map(|m| {
crate::tdlib::ReplyInfo { crate::tdlib::ReplyInfo {
message_id: m.id, message_id: m.id(),
sender_name: m.sender_name.clone(), sender_name: m.sender_name().to_string(),
text: m.content.clone(), text: m.text().to_string(),
} }
}); });
app.message_input.clear(); app.message_input.clear();
@@ -741,10 +741,10 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
// Показать модалку подтверждения удаления // Показать модалку подтверждения удаления
if let Some(msg) = app.get_selected_message() { if let Some(msg) = app.get_selected_message() {
let can_delete = let can_delete =
msg.can_be_deleted_only_for_self || msg.can_be_deleted_for_all_users; msg.can_be_deleted_only_for_self() || msg.can_be_deleted_for_all_users();
if can_delete { if can_delete {
app.chat_state = crate::app::ChatState::DeleteConfirmation { app.chat_state = crate::app::ChatState::DeleteConfirmation {
message_id: msg.id, message_id: msg.id(),
}; };
} }
} }
@@ -775,7 +775,7 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
// Открыть emoji picker для добавления реакции // Открыть emoji picker для добавления реакции
if let Some(msg) = app.get_selected_message() { if let Some(msg) = app.get_selected_message() {
let chat_id = app.selected_chat_id.unwrap(); let chat_id = app.selected_chat_id.unwrap();
let message_id = msg.id; let message_id = msg.id();
app.status_message = Some("Загрузка реакций...".to_string()); app.status_message = Some("Загрузка реакций...".to_string());
app.needs_redraw = true; app.needs_redraw = true;
@@ -942,7 +942,7 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
.td_client .td_client
.current_chat_messages() .current_chat_messages()
.first() .first()
.map(|m| m.id) .map(|m| m.id())
.unwrap_or(0); .unwrap_or(0);
if let Some(chat_id) = app.get_selected_chat_id() { if let Some(chat_id) = app.get_selected_chat_id() {
// Подгружаем больше сообщений если скролл близко к верху // Подгружаем больше сообщений если скролл близко к верху
@@ -1051,7 +1051,7 @@ fn format_message_for_clipboard(msg: &crate::tdlib::MessageInfo) -> String {
} }
// Добавляем основной текст с markdown форматированием // Добавляем основной текст с markdown форматированием
result.push_str(&convert_entities_to_markdown(&msg.content, &msg.entities)); result.push_str(&convert_entities_to_markdown(msg.text(), msg.entities()));
result result
} }

View File

@@ -422,8 +422,8 @@ impl TdClient {
// Если это текущий открытый чат — обновляем is_read у сообщений // Если это текущий открытый чат — обновляем is_read у сообщений
if Some(ChatId::new(update.chat_id)) == self.current_chat_id() { if Some(ChatId::new(update.chat_id)) == self.current_chat_id() {
for msg in self.current_chat_messages_mut().iter_mut() { for msg in self.current_chat_messages_mut().iter_mut() {
if msg.is_outgoing && msg.id <= last_read_msg_id { if msg.is_outgoing() && msg.id() <= last_read_msg_id {
msg.is_read = true; msg.state.is_read = true;
} }
} }
} }
@@ -477,7 +477,7 @@ impl TdClient {
let existing_idx = self let existing_idx = self
.current_chat_messages() .current_chat_messages()
.iter() .iter()
.position(|m| m.id == msg_info.id); .position(|m| m.id() == msg_info.id());
match existing_idx { match existing_idx {
Some(idx) => { Some(idx) => {
@@ -646,10 +646,10 @@ impl TdClient {
if let Some(msg) = self if let Some(msg) = self
.current_chat_messages_mut() .current_chat_messages_mut()
.iter_mut() .iter_mut()
.find(|m| m.id == MessageId::new(update.message_id)) .find(|m| m.id() == MessageId::new(update.message_id))
{ {
// Извлекаем реакции из interaction_info // Извлекаем реакции из interaction_info
msg.reactions = update msg.interactions.reactions = update
.interaction_info .interaction_info
.as_ref() .as_ref()
.and_then(|info| info.reactions.as_ref()) .and_then(|info| info.reactions.as_ref())
@@ -870,22 +870,22 @@ impl TdClient {
// Извлекаем реакции // Извлекаем реакции
let reactions = self.extract_reactions(message); let reactions = self.extract_reactions(message);
MessageInfo { MessageInfo::new(
id: message_id, message_id,
sender_name, sender_name,
is_outgoing: message.is_outgoing, message.is_outgoing,
content, content,
entities, entities,
date: message.date, message.date,
edit_date: message.edit_date, message.edit_date,
is_read, is_read,
can_be_edited: message.can_be_edited, message.can_be_edited,
can_be_deleted_only_for_self: message.can_be_deleted_only_for_self, message.can_be_deleted_only_for_self,
can_be_deleted_for_all_users: message.can_be_deleted_for_all_users, message.can_be_deleted_for_all_users,
reply_to, reply_to,
forward_from, forward_from,
reactions, reactions,
} )
} }
/// Извлекает информацию о reply из сообщения /// Извлекает информацию о reply из сообщения
@@ -902,8 +902,8 @@ impl TdClient {
let reply_msg_id = MessageId::new(reply.message_id); let reply_msg_id = MessageId::new(reply.message_id);
self.current_chat_messages() self.current_chat_messages()
.iter() .iter()
.find(|m| m.id == reply_msg_id) .find(|m| m.id() == reply_msg_id)
.map(|m| m.sender_name.clone()) .map(|m| m.sender_name().to_string())
.unwrap_or_else(|| "...".to_string()) .unwrap_or_else(|| "...".to_string())
}; };
@@ -917,8 +917,8 @@ impl TdClient {
// Пробуем найти в текущих сообщениях // Пробуем найти в текущих сообщениях
self.current_chat_messages() self.current_chat_messages()
.iter() .iter()
.find(|m| m.id == reply_msg_id) .find(|m| m.id() == reply_msg_id)
.map(|m| m.content.clone()) .map(|m| m.text().to_string())
.unwrap_or_default() .unwrap_or_default()
}; };
@@ -996,7 +996,7 @@ impl TdClient {
let msg_data: std::collections::HashMap<i64, (String, String)> = self let msg_data: std::collections::HashMap<i64, (String, String)> = self
.current_chat_messages() .current_chat_messages()
.iter() .iter()
.map(|m| (m.id, (m.sender_name.clone(), m.content.clone()))) .map(|m| (m.id(), (m.sender_name().to_string(), m.text().to_string())))
.collect(); .collect();
// Обновляем reply_to для сообщений с неполными данными // Обновляем reply_to для сообщений с неполными данными

View File

@@ -57,17 +57,28 @@ pub struct ReactionInfo {
pub is_chosen: bool, pub is_chosen: bool,
} }
/// Метаданные сообщения (ID, отправитель, время)
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MessageInfo { pub struct MessageMetadata {
pub id: MessageId, pub id: MessageId,
pub sender_name: String, pub sender_name: String,
pub is_outgoing: bool,
pub content: String,
/// Сущности форматирования (bold, italic, code и т.д.)
pub entities: Vec<TextEntity>,
pub date: i32, pub date: i32,
/// Дата редактирования (0 если не редактировалось) /// Дата редактирования (0 если не редактировалось)
pub edit_date: i32, pub edit_date: i32,
}
/// Контент сообщения (текст и форматирование)
#[derive(Debug, Clone)]
pub struct MessageContent {
pub text: String,
/// Сущности форматирования (bold, italic, code и т.д.)
pub entities: Vec<TextEntity>,
}
/// Состояние и права доступа к сообщению
#[derive(Debug, Clone)]
pub struct MessageState {
pub is_outgoing: bool,
pub is_read: bool, pub is_read: bool,
/// Можно ли редактировать сообщение /// Можно ли редактировать сообщение
pub can_be_edited: bool, pub can_be_edited: bool,
@@ -75,6 +86,11 @@ pub struct MessageInfo {
pub can_be_deleted_only_for_self: bool, pub can_be_deleted_only_for_self: bool,
/// Можно ли удалить для всех /// Можно ли удалить для всех
pub can_be_deleted_for_all_users: bool, pub can_be_deleted_for_all_users: bool,
}
/// Взаимодействия с сообщением (reply, forward, reactions)
#[derive(Debug, Clone)]
pub struct MessageInteractions {
/// Информация о reply (если это ответ на сообщение) /// Информация о reply (если это ответ на сообщение)
pub reply_to: Option<ReplyInfo>, pub reply_to: Option<ReplyInfo>,
/// Информация о forward (если сообщение переслано) /// Информация о forward (если сообщение переслано)
@@ -83,6 +99,120 @@ pub struct MessageInfo {
pub reactions: Vec<ReactionInfo>, pub reactions: Vec<ReactionInfo>,
} }
#[derive(Debug, Clone)]
pub struct MessageInfo {
pub metadata: MessageMetadata,
pub content: MessageContent,
pub state: MessageState,
pub interactions: MessageInteractions,
}
impl MessageInfo {
/// Создать новое сообщение
pub fn new(
id: MessageId,
sender_name: String,
is_outgoing: bool,
content: String,
entities: Vec<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>,
) -> Self {
Self {
metadata: MessageMetadata {
id,
sender_name,
date,
edit_date,
},
content: MessageContent {
text: content,
entities,
},
state: MessageState {
is_outgoing,
is_read,
can_be_edited,
can_be_deleted_only_for_self,
can_be_deleted_for_all_users,
},
interactions: MessageInteractions {
reply_to,
forward_from,
reactions,
},
}
}
// Удобные getter'ы для частых операций
pub fn id(&self) -> MessageId {
self.metadata.id
}
pub fn sender_name(&self) -> &str {
&self.metadata.sender_name
}
pub fn date(&self) -> i32 {
self.metadata.date
}
pub fn edit_date(&self) -> i32 {
self.metadata.edit_date
}
pub fn is_edited(&self) -> bool {
self.metadata.edit_date > 0
}
pub fn text(&self) -> &str {
&self.content.text
}
pub fn entities(&self) -> &[TextEntity] {
&self.content.entities
}
pub fn is_outgoing(&self) -> bool {
self.state.is_outgoing
}
pub fn is_read(&self) -> bool {
self.state.is_read
}
pub fn can_be_edited(&self) -> bool {
self.state.can_be_edited
}
pub fn can_be_deleted_only_for_self(&self) -> bool {
self.state.can_be_deleted_only_for_self
}
pub fn can_be_deleted_for_all_users(&self) -> bool {
self.state.can_be_deleted_for_all_users
}
pub fn reply_to(&self) -> Option<&ReplyInfo> {
self.interactions.reply_to.as_ref()
}
pub fn forward_from(&self) -> Option<&ForwardInfo> {
self.interactions.forward_from.as_ref()
}
pub fn reactions(&self) -> &[ReactionInfo] {
&self.interactions.reactions
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FolderInfo { pub struct FolderInfo {
pub id: i32, pub id: i32,

View File

@@ -420,13 +420,13 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
// Pinned bar (если есть закреплённое сообщение) // Pinned bar (если есть закреплённое сообщение)
if let Some(pinned_msg) = &app.td_client.current_pinned_message() { if let Some(pinned_msg) = &app.td_client.current_pinned_message() {
let pinned_preview: String = pinned_msg.content.chars().take(40).collect(); let pinned_preview: String = pinned_msg.text().chars().take(40).collect();
let ellipsis = if pinned_msg.content.chars().count() > 40 { let ellipsis = if pinned_msg.text().chars().count() > 40 {
"..." "..."
} else { } else {
"" ""
}; };
let pinned_datetime = crate::utils::format_datetime(pinned_msg.date); let pinned_datetime = crate::utils::format_datetime(pinned_msg.date());
let pinned_text = format!("📌 {} {}{}", pinned_datetime, pinned_preview, ellipsis); let pinned_text = format!("📌 {} {}{}", pinned_datetime, pinned_preview, ellipsis);
let pinned_hint = "Ctrl+P"; let pinned_hint = "Ctrl+P";
@@ -454,26 +454,26 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
let mut last_sender: Option<(bool, String)> = None; // (is_outgoing, sender_name) let mut last_sender: Option<(bool, String)> = None; // (is_outgoing, sender_name)
// ID выбранного сообщения для подсветки // ID выбранного сообщения для подсветки
let selected_msg_id = app.get_selected_message().map(|m| m.id); let selected_msg_id = app.get_selected_message().map(|m| m.id());
// Номер строки, где начинается выбранное сообщение (для автоскролла) // Номер строки, где начинается выбранное сообщение (для автоскролла)
let mut selected_msg_line: Option<usize> = None; let mut selected_msg_line: Option<usize> = None;
for msg in app.td_client.current_chat_messages() { for msg in app.td_client.current_chat_messages() {
// Проверяем, выбрано ли это сообщение // Проверяем, выбрано ли это сообщение
let is_selected = selected_msg_id == Some(msg.id); let is_selected = selected_msg_id == Some(msg.id());
// Запоминаем строку начала выбранного сообщения // Запоминаем строку начала выбранного сообщения
if is_selected { if is_selected {
selected_msg_line = Some(lines.len()); selected_msg_line = Some(lines.len());
} }
// Проверяем, нужно ли добавить разделитель даты // Проверяем, нужно ли добавить разделитель даты
let msg_day = get_day(msg.date); let msg_day = get_day(msg.date());
if last_day != Some(msg_day) { if last_day != Some(msg_day) {
if last_day.is_some() { if last_day.is_some() {
lines.push(Line::from("")); // Пустая строка перед разделителем lines.push(Line::from("")); // Пустая строка перед разделителем
} }
// Добавляем разделитель даты по центру // Добавляем разделитель даты по центру
let date_str = format_date(msg.date); let date_str = format_date(msg.date());
let date_line = format!("──────── {} ────────", date_str); let date_line = format!("──────── {} ────────", date_str);
let padding = content_width.saturating_sub(date_line.chars().count()) / 2; let padding = content_width.saturating_sub(date_line.chars().count()) / 2;
lines.push(Line::from(vec![ lines.push(Line::from(vec![
@@ -485,13 +485,13 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
last_sender = None; // Сбрасываем отправителя при смене дня last_sender = None; // Сбрасываем отправителя при смене дня
} }
let sender_name = if msg.is_outgoing { let sender_name = if msg.is_outgoing() {
"Вы".to_string() "Вы".to_string()
} else { } else {
msg.sender_name.clone() msg.sender_name().to_string()
}; };
let current_sender = (msg.is_outgoing, sender_name.clone()); let current_sender = (msg.is_outgoing(), sender_name.clone());
// Проверяем, нужно ли показать заголовок отправителя // Проверяем, нужно ли показать заголовок отправителя
let show_sender_header = last_sender.as_ref() != Some(&current_sender); let show_sender_header = last_sender.as_ref() != Some(&current_sender);
@@ -502,7 +502,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
lines.push(Line::from("")); lines.push(Line::from(""));
} }
let sender_style = if msg.is_outgoing { let sender_style = if msg.is_outgoing() {
Style::default() Style::default()
.fg(Color::Green) .fg(Color::Green)
.add_modifier(Modifier::BOLD) .add_modifier(Modifier::BOLD)
@@ -512,7 +512,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
.add_modifier(Modifier::BOLD) .add_modifier(Modifier::BOLD)
}; };
if msg.is_outgoing { if msg.is_outgoing() {
// Заголовок "Вы" справа // Заголовок "Вы" справа
let header_text = format!("{} ────────────────", sender_name); let header_text = format!("{} ────────────────", sender_name);
let header_len = header_text.chars().count(); let header_len = header_text.chars().count();
@@ -534,12 +534,12 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
} }
// Форматируем время (HH:MM) с учётом timezone из config // Форматируем время (HH:MM) с учётом timezone из config
let time = format_timestamp_with_tz(msg.date, &app.config.general.timezone); let time = format_timestamp_with_tz(msg.date(), &app.config.general.timezone);
// Цвет сообщения (из config или жёлтый если выбрано) // Цвет сообщения (из config или жёлтый если выбрано)
let msg_color = if is_selected { let msg_color = if is_selected {
app.config.parse_color(&app.config.colors.selected_message) app.config.parse_color(&app.config.colors.selected_message)
} else if msg.is_outgoing { } else if msg.is_outgoing() {
app.config.parse_color(&app.config.colors.outgoing_message) app.config.parse_color(&app.config.colors.outgoing_message)
} else { } else {
app.config.parse_color(&app.config.colors.incoming_message) app.config.parse_color(&app.config.colors.incoming_message)
@@ -550,11 +550,11 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
let marker_len = selection_marker.chars().count(); let marker_len = selection_marker.chars().count();
// Отображаем forward если есть // Отображаем forward если есть
if let Some(forward) = &msg.forward_from { if let Some(forward) = msg.forward_from() {
let forward_line = format!("↪ Переслано от {}", forward.sender_name); let forward_line = format!("↪ Переслано от {}", forward.sender_name);
let forward_len = forward_line.chars().count(); let forward_len = forward_line.chars().count();
if msg.is_outgoing { if msg.is_outgoing() {
// Forward справа для исходящих // Forward справа для исходящих
let padding = content_width.saturating_sub(forward_len + 1); let padding = content_width.saturating_sub(forward_len + 1);
lines.push(Line::from(vec![ lines.push(Line::from(vec![
@@ -571,7 +571,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
} }
// Отображаем reply если есть // Отображаем reply если есть
if let Some(reply) = &msg.reply_to { if let Some(reply) = msg.reply_to() {
let reply_text: String = reply.text.chars().take(40).collect(); let reply_text: String = reply.text.chars().take(40).collect();
let ellipsis = if reply.text.chars().count() > 40 { let ellipsis = if reply.text.chars().count() > 40 {
"..." "..."
@@ -581,7 +581,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
let reply_line = format!("{}: {}{}", reply.sender_name, reply_text, ellipsis); let reply_line = format!("{}: {}{}", reply.sender_name, reply_text, ellipsis);
let reply_len = reply_line.chars().count(); let reply_len = reply_line.chars().count();
if msg.is_outgoing { if msg.is_outgoing() {
// Reply справа для исходящих // Reply справа для исходящих
let padding = content_width.saturating_sub(reply_len + 1); let padding = content_width.saturating_sub(reply_len + 1);
lines.push(Line::from(vec![ lines.push(Line::from(vec![
@@ -597,17 +597,17 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
} }
} }
if msg.is_outgoing { if msg.is_outgoing() {
// Исходящие: справа, формат "текст (HH:MM ✎ ✓✓)" // Исходящие: справа, формат "текст (HH:MM ✎ ✓✓)"
let read_mark = if msg.is_read { "✓✓" } else { "" }; let read_mark = if msg.is_read() { "✓✓" } else { "" };
let edit_mark = if msg.edit_date > 0 { "" } else { "" }; let edit_mark = if msg.is_edited() { "" } else { "" };
let time_mark = format!("({} {}{})", time, edit_mark, read_mark); let time_mark = format!("({} {}{})", time, edit_mark, read_mark);
let time_mark_len = time_mark.chars().count() + 1; // +1 для пробела let time_mark_len = time_mark.chars().count() + 1; // +1 для пробела
// Максимальная ширина для текста сообщения (оставляем место для time_mark и маркера) // Максимальная ширина для текста сообщения (оставляем место для time_mark и маркера)
let max_msg_width = content_width.saturating_sub(time_mark_len + marker_len + 2); let max_msg_width = content_width.saturating_sub(time_mark_len + marker_len + 2);
let wrapped_lines = wrap_text_with_offsets(&msg.content, max_msg_width); let wrapped_lines = wrap_text_with_offsets(msg.text(), max_msg_width);
let total_wrapped = wrapped_lines.len(); let total_wrapped = wrapped_lines.len();
for (i, wrapped) in wrapped_lines.into_iter().enumerate() { for (i, wrapped) in wrapped_lines.into_iter().enumerate() {
@@ -616,7 +616,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
// Получаем entities для этой строки // Получаем entities для этой строки
let line_entities = adjust_entities_for_substring( let line_entities = adjust_entities_for_substring(
&msg.entities, msg.entities(),
wrapped.start_offset, wrapped.start_offset,
line_len, line_len,
); );
@@ -662,21 +662,21 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
} }
} else { } else {
// Входящие: слева, формат "(HH:MM ✎) текст" // Входящие: слева, формат "(HH:MM ✎) текст"
let edit_mark = if msg.edit_date > 0 { "" } else { "" }; let edit_mark = if msg.is_edited() { "" } else { "" };
let time_str = format!("({}{})", time, edit_mark); let time_str = format!("({}{})", time, edit_mark);
let time_prefix_len = time_str.chars().count() + 2; // " (HH:MM) " let time_prefix_len = time_str.chars().count() + 2; // " (HH:MM) "
// Максимальная ширина для текста // Максимальная ширина для текста
let max_msg_width = content_width.saturating_sub(time_prefix_len + 1); let max_msg_width = content_width.saturating_sub(time_prefix_len + 1);
let wrapped_lines = wrap_text_with_offsets(&msg.content, max_msg_width); let wrapped_lines = wrap_text_with_offsets(msg.text(), max_msg_width);
for (i, wrapped) in wrapped_lines.into_iter().enumerate() { for (i, wrapped) in wrapped_lines.into_iter().enumerate() {
let line_len = wrapped.text.chars().count(); let line_len = wrapped.text.chars().count();
// Получаем entities для этой строки // Получаем entities для этой строки
let line_entities = adjust_entities_for_substring( let line_entities = adjust_entities_for_substring(
&msg.entities, msg.entities(),
wrapped.start_offset, wrapped.start_offset,
line_len, line_len,
); );
@@ -714,10 +714,10 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
} }
// Отображаем реакции под сообщением // Отображаем реакции под сообщением
if !msg.reactions.is_empty() { if !msg.reactions().is_empty() {
let mut reaction_spans = vec![]; let mut reaction_spans = vec![];
for reaction in &msg.reactions { for reaction in &msg.reactions() {
if !reaction_spans.is_empty() { if !reaction_spans.is_empty() {
reaction_spans.push(Span::raw(" ")); reaction_spans.push(Span::raw(" "));
} }
@@ -749,7 +749,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
} }
// Выравниваем реакции в зависимости от типа сообщения // Выравниваем реакции в зависимости от типа сообщения
if msg.is_outgoing { if msg.is_outgoing() {
// Реакции справа для исходящих // Реакции справа для исходящих
let reactions_text: String = reaction_spans let reactions_text: String = reaction_spans
.iter() .iter()
@@ -815,8 +815,8 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
let forward_preview = app let forward_preview = app
.get_forwarding_message() .get_forwarding_message()
.map(|m| { .map(|m| {
let text_preview: String = m.content.chars().take(40).collect(); let text_preview: String = m.text().chars().take(40).collect();
let ellipsis = if m.content.chars().count() > 40 { let ellipsis = if m.text().chars().count() > 40 {
"..." "..."
} else { } else {
"" ""
@@ -875,10 +875,10 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
let sender = if m.is_outgoing { let sender = if m.is_outgoing {
"Вы" "Вы"
} else { } else {
&m.sender_name m.sender_name()
}; };
let text_preview: String = m.content.chars().take(30).collect(); let text_preview: String = m.text().chars().take(30).collect();
let ellipsis = if m.content.chars().count() > 30 { let ellipsis = if m.text().chars().count() > 30 {
"..." "..."
} else { } else {
"" ""
@@ -1056,15 +1056,15 @@ fn render_search_mode(f: &mut Frame, area: Rect, app: &App) {
// Маркер выбора, имя и дата // Маркер выбора, имя и дата
let marker = if is_selected { "" } else { " " }; let marker = if is_selected { "" } else { " " };
let sender_color = if msg.is_outgoing { let sender_color = if msg.is_outgoing() {
Color::Green Color::Green
} else { } else {
Color::Cyan Color::Cyan
}; };
let sender_name = if msg.is_outgoing { let sender_name = if msg.is_outgoing() {
"Вы".to_string() "Вы".to_string()
} else { } else {
msg.sender_name.clone() msg.sender_name().to_string()
}; };
lines.push(Line::from(vec![ lines.push(Line::from(vec![
@@ -1081,7 +1081,7 @@ fn render_search_mode(f: &mut Frame, area: Rect, app: &App) {
.add_modifier(Modifier::BOLD), .add_modifier(Modifier::BOLD),
), ),
Span::styled( Span::styled(
format!("({})", crate::utils::format_datetime(msg.date)), format!("({})", crate::utils::format_datetime(msg.date())),
Style::default().fg(Color::Gray), Style::default().fg(Color::Gray),
), ),
])); ]));
@@ -1093,7 +1093,7 @@ fn render_search_mode(f: &mut Frame, area: Rect, app: &App) {
Color::White Color::White
}; };
let max_width = content_width.saturating_sub(4); let max_width = content_width.saturating_sub(4);
let wrapped = wrap_text_with_offsets(&msg.content, max_width); let wrapped = wrap_text_with_offsets(msg.text(), max_width);
let wrapped_count = wrapped.len(); let wrapped_count = wrapped.len();
for wrapped_line in wrapped.into_iter().take(2) { for wrapped_line in wrapped.into_iter().take(2) {
@@ -1222,15 +1222,15 @@ fn render_pinned_mode(f: &mut Frame, area: Rect, app: &App) {
// Маркер выбора и имя отправителя // Маркер выбора и имя отправителя
let marker = if is_selected { "" } else { " " }; let marker = if is_selected { "" } else { " " };
let sender_color = if msg.is_outgoing { let sender_color = if msg.is_outgoing() {
Color::Green Color::Green
} else { } else {
Color::Cyan Color::Cyan
}; };
let sender_name = if msg.is_outgoing { let sender_name = if msg.is_outgoing() {
"Вы".to_string() "Вы".to_string()
} else { } else {
msg.sender_name.clone() msg.sender_name().to_string()
}; };
lines.push(Line::from(vec![ lines.push(Line::from(vec![
@@ -1247,7 +1247,7 @@ fn render_pinned_mode(f: &mut Frame, area: Rect, app: &App) {
.add_modifier(Modifier::BOLD), .add_modifier(Modifier::BOLD),
), ),
Span::styled( Span::styled(
format!("({})", crate::utils::format_datetime(msg.date)), format!("({})", crate::utils::format_datetime(msg.date())),
Style::default().fg(Color::Gray), Style::default().fg(Color::Gray),
), ),
])); ]));
@@ -1259,7 +1259,7 @@ fn render_pinned_mode(f: &mut Frame, area: Rect, app: &App) {
Color::White Color::White
}; };
let max_width = content_width.saturating_sub(4); let max_width = content_width.saturating_sub(4);
let wrapped = wrap_text_with_offsets(&msg.content, max_width); let wrapped = wrap_text_with_offsets(msg.text(), max_width);
let wrapped_count = wrapped.len(); let wrapped_count = wrapped.len();
for wrapped_line in wrapped.into_iter().take(3) { for wrapped_line in wrapped.into_iter().take(3) {

View File

@@ -106,7 +106,7 @@ fn format_message_for_test(msg: &tele_tui::tdlib::MessageInfo) -> String {
} }
// Добавляем основной текст // Добавляем основной текст
result.push_str(&msg.content); result.push_str(msg.text());
result result
} }

View File

@@ -52,7 +52,7 @@ fn test_delete_multiple_messages() {
let messages = client.get_messages(123); let messages = client.get_messages(123);
assert_eq!(messages.len(), 1); assert_eq!(messages.len(), 1);
assert_eq!(messages[0].id, msg2_id); assert_eq!(messages[0].id, msg2_id);
assert_eq!(messages[0].content, "Message 2"); assert_eq!(messages[0].content.text(), "Message 2");
} }
/// Test: Удаление только своих сообщений (проверка через can_be_deleted_for_all_users) /// Test: Удаление только своих сообщений (проверка через can_be_deleted_for_all_users)
@@ -145,5 +145,5 @@ fn test_cancel_delete_keeps_message() {
// Сообщение на месте // Сообщение на месте
let messages = client.get_messages(123); let messages = client.get_messages(123);
assert_eq!(messages[0].id, msg_id); assert_eq!(messages[0].id, msg_id);
assert_eq!(messages[0].content, "Keep me"); assert_eq!(messages[0].content.text(), "Keep me");
} }

View File

@@ -24,7 +24,7 @@ fn test_edit_message_changes_text() {
// Проверяем что текст сообщения изменился // Проверяем что текст сообщения изменился
let messages = client.get_messages(123); let messages = client.get_messages(123);
assert_eq!(messages.len(), 1); assert_eq!(messages.len(), 1);
assert_eq!(messages[0].content, "Edited text"); assert_eq!(messages[0].content.text(), "Edited text");
} }
/// Test: Редактирование устанавливает edit_date /// Test: Редактирование устанавливает edit_date
@@ -97,7 +97,7 @@ fn test_multiple_edits_of_same_message() {
// Проверяем что сообщение содержит последнюю версию // Проверяем что сообщение содержит последнюю версию
let messages = client.get_messages(123); let messages = client.get_messages(123);
assert_eq!(messages.len(), 1); assert_eq!(messages.len(), 1);
assert_eq!(messages[0].content, "Final version"); assert_eq!(messages[0].content.text(), "Final version");
} }
/// Test: Редактирование несуществующего сообщения (ничего не происходит) /// Test: Редактирование несуществующего сообщения (ничего не происходит)
@@ -129,21 +129,21 @@ fn test_edit_history_tracking() {
// Сохраняем original // Сохраняем original
let messages_before = client.get_messages(123); let messages_before = client.get_messages(123);
let original = messages_before[0].content.clone(); let original = messages_before[0].text().to_string();
// Редактируем // Редактируем
client.edit_message(123, msg_id, "Edited".to_string()); client.edit_message(123, msg_id, "Edited".to_string());
// Проверяем что изменилось // Проверяем что изменилось
let messages_edited = client.get_messages(123); let messages_edited = client.get_messages(123);
assert_eq!(messages_edited[0].content, "Edited"); assert_eq!(messages_edited[0].content.text(), "Edited");
// Можем "отменить" редактирование вернув original // Можем "отменить" редактирование вернув original
client.edit_message(123, msg_id, original); client.edit_message(123, msg_id, original);
// Проверяем что вернулось // Проверяем что вернулось
let messages_restored = client.get_messages(123); let messages_restored = client.get_messages(123);
assert_eq!(messages_restored[0].content, "Original"); assert_eq!(messages_restored[0].content.text(), "Original");
// История показывает 2 редактирования // История показывает 2 редактирования
assert_eq!(client.edited_messages().len(), 2); assert_eq!(client.edited_messages().len(), 2);

View File

@@ -156,9 +156,9 @@ impl FakeTdClient {
// Обновляем сообщение в списке // Обновляем сообщение в списке
if let Some(messages) = self.messages.get_mut(&chat_id) { if let Some(messages) = self.messages.get_mut(&chat_id) {
if let Some(msg) = messages.iter_mut().find(|m| m.id == message_id) { if let Some(msg) = messages.iter_mut().find(|m| m.id() == message_id) {
msg.content = new_text; msg.content.text = new_text;
msg.edit_date = msg.date + 60; msg.metadata.edit_date = msg.metadata.date + 60;
} }
} }
} }

View File

@@ -188,22 +188,22 @@ impl TestMessageBuilder {
} }
pub fn build(self) -> MessageInfo { pub fn build(self) -> MessageInfo {
MessageInfo { MessageInfo::new(
id: MessageId::new(self.id), MessageId::new(self.id),
sender_name: self.sender_name, self.sender_name,
is_outgoing: self.is_outgoing, self.is_outgoing,
content: self.content, self.content,
entities: self.entities, self.entities,
date: self.date, self.date,
edit_date: self.edit_date, self.edit_date,
is_read: self.is_read, self.is_read,
can_be_edited: self.can_be_edited, self.can_be_edited,
can_be_deleted_only_for_self: self.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, self.can_be_deleted_for_all_users,
reply_to: self.reply_to, self.reply_to,
forward_from: self.forward_from, self.forward_from,
reactions: self.reactions, self.reactions,
} )
} }
} }

View File

@@ -223,6 +223,6 @@ fn test_load_older_messages_on_scroll_up() {
// Теперь должно быть 15 сообщений // Теперь должно быть 15 сообщений
assert_eq!(client.get_messages(123).len(), 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)[0].content.text(), "Msg 81");
assert_eq!(client.get_messages(123)[14].content, "Msg 100"); assert_eq!(client.get_messages(123)[14].content.text(), "Msg 100");
} }

View File

@@ -29,7 +29,7 @@ fn test_reply_creates_message_with_reply_to() {
let messages = client.get_messages(123); let messages = client.get_messages(123);
assert_eq!(messages.len(), 2); assert_eq!(messages.len(), 2);
assert_eq!(messages[1].id, reply_id); assert_eq!(messages[1].id, reply_id);
assert_eq!(messages[1].content, "Answer!"); assert_eq!(messages[1].content.text(), "Answer!");
} }
/// Test: Reply отображает превью оригинального сообщения /// Test: Reply отображает превью оригинального сообщения
@@ -76,7 +76,7 @@ fn test_cancel_reply_sends_without_reply_to() {
assert_eq!(client.sent_messages()[0].reply_to, None); assert_eq!(client.sent_messages()[0].reply_to, None);
let messages = client.get_messages(123); let messages = client.get_messages(123);
assert_eq!(messages[1].content, "Regular message"); assert_eq!(messages[1].content.text(), "Regular message");
} }
/// Test: Forward создаёт сообщение с forward_from /// Test: Forward создаёт сообщение с forward_from

View File

@@ -99,12 +99,12 @@ fn test_search_messages_in_chat() {
let messages = client.get_messages(123); let messages = client.get_messages(123);
let found: Vec<_> = messages let found: Vec<_> = messages
.iter() .iter()
.filter(|m| m.content.to_lowercase().contains(&query)) .filter(|m| m.text().to_lowercase().contains(&query))
.collect(); .collect();
assert_eq!(found.len(), 2); assert_eq!(found.len(), 2);
assert_eq!(found[0].content, "Hello world"); assert_eq!(found[0].text(), "Hello world");
assert_eq!(found[1].content, "Hello again"); assert_eq!(found[1].text(), "Hello again");
} }
/// Test: Навигация по результатам поиска (n/N) /// Test: Навигация по результатам поиска (n/N)
@@ -124,7 +124,7 @@ fn test_navigate_search_results() {
let results: Vec<_> = messages let results: Vec<_> = messages
.iter() .iter()
.enumerate() .enumerate()
.filter(|(_, m)| m.content.to_lowercase().contains(&query)) .filter(|(_, m)| m.text().to_lowercase().contains(&query))
.collect(); .collect();
assert_eq!(results.len(), 3); assert_eq!(results.len(), 3);
@@ -135,17 +135,17 @@ fn test_navigate_search_results() {
// n - следующий результат // n - следующий результат
current_index = (current_index + 1) % results.len(); current_index = (current_index + 1) % results.len();
assert_eq!(current_index, 1); assert_eq!(current_index, 1);
assert_eq!(results[current_index].1.content, "Second match"); assert_eq!(results[current_index].1.text(), "Second match");
// n - ещё один // n - ещё один
current_index = (current_index + 1) % results.len(); current_index = (current_index + 1) % results.len();
assert_eq!(current_index, 2); assert_eq!(current_index, 2);
assert_eq!(results[current_index].1.content, "Third match"); assert_eq!(results[current_index].1.text(), "Third match");
// n - wrap around к первому // n - wrap around к первому
current_index = (current_index + 1) % results.len(); current_index = (current_index + 1) % results.len();
assert_eq!(current_index, 0); assert_eq!(current_index, 0);
assert_eq!(results[current_index].1.content, "First match"); assert_eq!(results[current_index].1.text(), "First match");
// N - предыдущий (wrap to last) // N - предыдущий (wrap to last)
current_index = if current_index == 0 { current_index = if current_index == 0 {
@@ -154,7 +154,7 @@ fn test_navigate_search_results() {
current_index - 1 current_index - 1
}; };
assert_eq!(current_index, 2); assert_eq!(current_index, 2);
assert_eq!(results[current_index].1.content, "Third match"); assert_eq!(results[current_index].1.text(), "Third match");
} }
/// Test: Поиск с учётом регистра (case-insensitive) /// Test: Поиск с учётом регистра (case-insensitive)
@@ -173,7 +173,7 @@ fn test_search_case_insensitive() {
let messages = client.get_messages(123); let messages = client.get_messages(123);
let found: Vec<_> = messages let found: Vec<_> = messages
.iter() .iter()
.filter(|m| m.content.to_lowercase().contains(&query)) .filter(|m| m.text().to_lowercase().contains(&query))
.collect(); .collect();
// Все 3 варианта должны найтись // Все 3 варианта должны найтись
@@ -195,7 +195,7 @@ fn test_search_no_results() {
let messages = client.get_messages(123); let messages = client.get_messages(123);
let found: Vec<_> = messages let found: Vec<_> = messages
.iter() .iter()
.filter(|m| m.content.to_lowercase().contains(&query)) .filter(|m| m.text().to_lowercase().contains(&query))
.collect(); .collect();
assert_eq!(found.len(), 0); assert_eq!(found.len(), 0);

View File

@@ -25,7 +25,7 @@ fn test_send_text_message() {
let messages = client.get_messages(123); let messages = client.get_messages(123);
assert_eq!(messages.len(), 1); assert_eq!(messages.len(), 1);
assert_eq!(messages[0].id, msg_id); assert_eq!(messages[0].id, msg_id);
assert_eq!(messages[0].content, "Hello, Mom!"); assert_eq!(messages.text(), "Hello, Mom!");
assert_eq!(messages[0].is_outgoing, true); assert_eq!(messages[0].is_outgoing, true);
} }
@@ -52,9 +52,9 @@ fn test_send_multiple_messages_updates_list() {
assert_eq!(messages[0].id, msg1_id); assert_eq!(messages[0].id, msg1_id);
assert_eq!(messages[1].id, msg2_id); assert_eq!(messages[1].id, msg2_id);
assert_eq!(messages[2].id, msg3_id); assert_eq!(messages[2].id, msg3_id);
assert_eq!(messages[0].content, "Message 1"); assert_eq!(messages.text(), "Message 1");
assert_eq!(messages[1].content, "Message 2"); assert_eq!(messages.text(), "Message 2");
assert_eq!(messages[2].content, "Message 3"); assert_eq!(messages.text(), "Message 3");
} }
/// Test: Отправка пустого сообщения (должно быть игнорировано на уровне App) /// Test: Отправка пустого сообщения (должно быть игнорировано на уровне App)
@@ -74,7 +74,7 @@ fn test_send_empty_message_technical() {
let messages = client.get_messages(123); let messages = client.get_messages(123);
assert_eq!(messages.len(), 1); assert_eq!(messages.len(), 1);
assert_eq!(messages[0].id, msg_id); assert_eq!(messages[0].id, msg_id);
assert_eq!(messages[0].content, ""); assert_eq!(messages.text(), "");
} }
/// Test: Отправка сообщения с форматированием (markdown сущности) /// Test: Отправка сообщения с форматированием (markdown сущности)
@@ -89,7 +89,7 @@ fn test_send_message_with_markdown() {
// Проверяем что текст сохранился как есть (парсинг markdown - отдельная логика) // Проверяем что текст сохранился как есть (парсинг markdown - отдельная логика)
let messages = client.get_messages(123); let messages = client.get_messages(123);
assert_eq!(messages.len(), 1); assert_eq!(messages.len(), 1);
assert_eq!(messages[0].content, text); assert_eq!(messages.text(), text);
} }
/// Test: Отправка сообщения в разные чаты /// Test: Отправка сообщения в разные чаты
@@ -112,12 +112,12 @@ fn test_send_messages_to_different_chats() {
// Проверяем что сообщения распределены по чатам // Проверяем что сообщения распределены по чатам
let chat123_messages = client.get_messages(123); let chat123_messages = client.get_messages(123);
assert_eq!(chat123_messages.len(), 2); assert_eq!(chat123_messages.len(), 2);
assert_eq!(chat123_messages[0].content, "Hello Mom"); assert_eq!(chat123_messages.text(), "Hello Mom");
assert_eq!(chat123_messages[1].content, "How are you?"); assert_eq!(chat123_messages.text(), "How are you?");
let chat456_messages = client.get_messages(456); let chat456_messages = client.get_messages(456);
assert_eq!(chat456_messages.len(), 1); assert_eq!(chat456_messages.len(), 1);
assert_eq!(chat456_messages[0].content, "Hello Boss"); assert_eq!(chat456_messages.text(), "Hello Boss");
} }
/// Test: Новое сообщение появляется в реальном времени (симуляция) /// Test: Новое сообщение появляется в реальном времени (симуляция)
@@ -141,6 +141,6 @@ fn test_receive_incoming_message() {
assert_eq!(messages.len(), 2); assert_eq!(messages.len(), 2);
assert_eq!(messages[0].is_outgoing, true); // Наше сообщение assert_eq!(messages[0].is_outgoing, true); // Наше сообщение
assert_eq!(messages[1].is_outgoing, false); // Входящее assert_eq!(messages[1].is_outgoing, false); // Входящее
assert_eq!(messages[1].content, "Hey there!"); assert_eq!(messages.text(), "Hey there!");
assert_eq!(messages[1].sender_name, "Alice"); assert_eq!(messages[1].sender_name, "Alice");
} }