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:
@@ -188,8 +188,8 @@ impl App {
|
||||
|
||||
// Сначала извлекаем данные из сообщения
|
||||
let msg_data = self.get_selected_message().and_then(|msg| {
|
||||
if msg.can_be_edited && msg.is_outgoing {
|
||||
Some((msg.id, msg.content.clone(), selected_idx.unwrap()))
|
||||
if msg.can_be_edited() && msg.is_outgoing() {
|
||||
Some((msg.id()(), msg.text().to_string(), selected_idx.unwrap()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -328,7 +328,7 @@ impl App {
|
||||
pub fn start_reply_to_selected(&mut self) -> bool {
|
||||
if let Some(msg) = self.get_selected_message() {
|
||||
self.chat_state = ChatState::Reply {
|
||||
message_id: msg.id,
|
||||
message_id: msg.id(),
|
||||
};
|
||||
return true;
|
||||
}
|
||||
@@ -351,7 +351,7 @@ impl App {
|
||||
self.td_client
|
||||
.current_chat_messages()
|
||||
.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 {
|
||||
if let Some(msg) = self.get_selected_message() {
|
||||
self.chat_state = ChatState::Forward {
|
||||
message_id: msg.id,
|
||||
message_id: msg.id(),
|
||||
selecting_chat: true,
|
||||
};
|
||||
// Сбрасываем выбор чата на первый
|
||||
@@ -388,7 +388,7 @@ impl App {
|
||||
self.td_client
|
||||
.current_chat_messages()
|
||||
.iter()
|
||||
.find(|m| m.id == id)
|
||||
.find(|m| m.id() == id)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -451,7 +451,7 @@ impl App {
|
||||
|
||||
/// Получить ID текущего pinned для перехода в историю
|
||||
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 ===
|
||||
@@ -522,7 +522,7 @@ impl App {
|
||||
|
||||
/// Получить ID выбранного результата для перехода
|
||||
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())
|
||||
}
|
||||
|
||||
/// Получить поисковый запрос из режима поиска
|
||||
|
||||
@@ -193,7 +193,7 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
|
||||
.td_client
|
||||
.current_chat_messages()
|
||||
.iter()
|
||||
.position(|m| m.id == msg_id);
|
||||
.position(|m| m.id() == msg_id);
|
||||
|
||||
if let Some(idx) = msg_index {
|
||||
let total = app.td_client.current_chat_messages().len();
|
||||
@@ -268,7 +268,7 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
|
||||
.td_client
|
||||
.current_chat_messages()
|
||||
.iter()
|
||||
.position(|m| m.id == msg_id);
|
||||
.position(|m| m.id() == msg_id);
|
||||
|
||||
if let Some(idx) = msg_index {
|
||||
// Вычисляем scroll offset чтобы показать сообщение
|
||||
@@ -381,8 +381,8 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
|
||||
.td_client
|
||||
.current_chat_messages()
|
||||
.iter()
|
||||
.find(|m| m.id == msg_id)
|
||||
.map(|m| m.can_be_deleted_for_all_users)
|
||||
.find(|m| m.id() == msg_id)
|
||||
.map(|m| m.can_be_deleted_for_all_users())
|
||||
.unwrap_or(false);
|
||||
|
||||
match timeout(
|
||||
@@ -399,7 +399,7 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
|
||||
// Удаляем из локального списка
|
||||
app.td_client
|
||||
.current_chat_messages_mut()
|
||||
.retain(|m| m.id != msg_id);
|
||||
.retain(|m| m.id() != msg_id);
|
||||
// Сбрасываем состояние
|
||||
app.chat_state = crate::app::ChatState::Normal;
|
||||
}
|
||||
@@ -582,11 +582,11 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
|
||||
.td_client
|
||||
.current_chat_messages_mut()
|
||||
.iter_mut()
|
||||
.find(|m| m.id == msg_id)
|
||||
.find(|m| m.id() == msg_id)
|
||||
{
|
||||
msg.content = edited_msg.content;
|
||||
msg.entities = edited_msg.entities;
|
||||
msg.edit_date = edited_msg.edit_date;
|
||||
msg.content.text = edited_msg.content.text;
|
||||
msg.content.entities = edited_msg.content.entities;
|
||||
msg.metadata.edit_date = edited_msg.metadata.edit_date;
|
||||
}
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
@@ -607,9 +607,9 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
|
||||
// Создаём ReplyInfo ДО отправки, пока сообщение точно доступно
|
||||
let reply_info = app.get_replying_to_message().map(|m| {
|
||||
crate::tdlib::ReplyInfo {
|
||||
message_id: m.id,
|
||||
sender_name: m.sender_name.clone(),
|
||||
text: m.content.clone(),
|
||||
message_id: m.id(),
|
||||
sender_name: m.sender_name().to_string(),
|
||||
text: m.text().to_string(),
|
||||
}
|
||||
});
|
||||
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() {
|
||||
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 {
|
||||
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 для добавления реакции
|
||||
if let Some(msg) = app.get_selected_message() {
|
||||
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.needs_redraw = true;
|
||||
@@ -942,7 +942,7 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
|
||||
.td_client
|
||||
.current_chat_messages()
|
||||
.first()
|
||||
.map(|m| m.id)
|
||||
.map(|m| m.id())
|
||||
.unwrap_or(0);
|
||||
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 форматированием
|
||||
result.push_str(&convert_entities_to_markdown(&msg.content, &msg.entities));
|
||||
result.push_str(&convert_entities_to_markdown(msg.text(), msg.entities()));
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
@@ -422,8 +422,8 @@ impl TdClient {
|
||||
// Если это текущий открытый чат — обновляем is_read у сообщений
|
||||
if Some(ChatId::new(update.chat_id)) == self.current_chat_id() {
|
||||
for msg in self.current_chat_messages_mut().iter_mut() {
|
||||
if msg.is_outgoing && msg.id <= last_read_msg_id {
|
||||
msg.is_read = true;
|
||||
if msg.is_outgoing() && msg.id() <= last_read_msg_id {
|
||||
msg.state.is_read = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -477,7 +477,7 @@ impl TdClient {
|
||||
let existing_idx = self
|
||||
.current_chat_messages()
|
||||
.iter()
|
||||
.position(|m| m.id == msg_info.id);
|
||||
.position(|m| m.id() == msg_info.id());
|
||||
|
||||
match existing_idx {
|
||||
Some(idx) => {
|
||||
@@ -646,10 +646,10 @@ impl TdClient {
|
||||
if let Some(msg) = self
|
||||
.current_chat_messages_mut()
|
||||
.iter_mut()
|
||||
.find(|m| m.id == MessageId::new(update.message_id))
|
||||
.find(|m| m.id() == MessageId::new(update.message_id))
|
||||
{
|
||||
// Извлекаем реакции из interaction_info
|
||||
msg.reactions = update
|
||||
msg.interactions.reactions = update
|
||||
.interaction_info
|
||||
.as_ref()
|
||||
.and_then(|info| info.reactions.as_ref())
|
||||
@@ -870,22 +870,22 @@ impl TdClient {
|
||||
// Извлекаем реакции
|
||||
let reactions = self.extract_reactions(message);
|
||||
|
||||
MessageInfo {
|
||||
id: message_id,
|
||||
MessageInfo::new(
|
||||
message_id,
|
||||
sender_name,
|
||||
is_outgoing: message.is_outgoing,
|
||||
message.is_outgoing,
|
||||
content,
|
||||
entities,
|
||||
date: message.date,
|
||||
edit_date: message.edit_date,
|
||||
message.date,
|
||||
message.edit_date,
|
||||
is_read,
|
||||
can_be_edited: message.can_be_edited,
|
||||
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_edited,
|
||||
message.can_be_deleted_only_for_self,
|
||||
message.can_be_deleted_for_all_users,
|
||||
reply_to,
|
||||
forward_from,
|
||||
reactions,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Извлекает информацию о reply из сообщения
|
||||
@@ -902,8 +902,8 @@ impl TdClient {
|
||||
let reply_msg_id = MessageId::new(reply.message_id);
|
||||
self.current_chat_messages()
|
||||
.iter()
|
||||
.find(|m| m.id == reply_msg_id)
|
||||
.map(|m| m.sender_name.clone())
|
||||
.find(|m| m.id() == reply_msg_id)
|
||||
.map(|m| m.sender_name().to_string())
|
||||
.unwrap_or_else(|| "...".to_string())
|
||||
};
|
||||
|
||||
@@ -917,8 +917,8 @@ impl TdClient {
|
||||
// Пробуем найти в текущих сообщениях
|
||||
self.current_chat_messages()
|
||||
.iter()
|
||||
.find(|m| m.id == reply_msg_id)
|
||||
.map(|m| m.content.clone())
|
||||
.find(|m| m.id() == reply_msg_id)
|
||||
.map(|m| m.text().to_string())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
@@ -996,7 +996,7 @@ impl TdClient {
|
||||
let msg_data: std::collections::HashMap<i64, (String, String)> = self
|
||||
.current_chat_messages()
|
||||
.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();
|
||||
|
||||
// Обновляем reply_to для сообщений с неполными данными
|
||||
|
||||
@@ -57,17 +57,28 @@ pub struct ReactionInfo {
|
||||
pub is_chosen: bool,
|
||||
}
|
||||
|
||||
/// Метаданные сообщения (ID, отправитель, время)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MessageInfo {
|
||||
pub struct MessageMetadata {
|
||||
pub id: MessageId,
|
||||
pub sender_name: String,
|
||||
pub is_outgoing: bool,
|
||||
pub content: String,
|
||||
/// Сущности форматирования (bold, italic, code и т.д.)
|
||||
pub entities: Vec<TextEntity>,
|
||||
pub date: i32,
|
||||
/// Дата редактирования (0 если не редактировалось)
|
||||
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 can_be_edited: bool,
|
||||
@@ -75,6 +86,11 @@ pub struct MessageInfo {
|
||||
pub can_be_deleted_only_for_self: bool,
|
||||
/// Можно ли удалить для всех
|
||||
pub can_be_deleted_for_all_users: bool,
|
||||
}
|
||||
|
||||
/// Взаимодействия с сообщением (reply, forward, reactions)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MessageInteractions {
|
||||
/// Информация о reply (если это ответ на сообщение)
|
||||
pub reply_to: Option<ReplyInfo>,
|
||||
/// Информация о forward (если сообщение переслано)
|
||||
@@ -83,6 +99,120 @@ pub struct MessageInfo {
|
||||
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)]
|
||||
pub struct FolderInfo {
|
||||
pub id: i32,
|
||||
|
||||
@@ -420,13 +420,13 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
|
||||
// Pinned bar (если есть закреплённое сообщение)
|
||||
if let Some(pinned_msg) = &app.td_client.current_pinned_message() {
|
||||
let pinned_preview: String = pinned_msg.content.chars().take(40).collect();
|
||||
let ellipsis = if pinned_msg.content.chars().count() > 40 {
|
||||
let pinned_preview: String = pinned_msg.text().chars().take(40).collect();
|
||||
let ellipsis = if pinned_msg.text().chars().count() > 40 {
|
||||
"..."
|
||||
} 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_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)
|
||||
|
||||
// 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;
|
||||
|
||||
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 {
|
||||
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.is_some() {
|
||||
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 padding = content_width.saturating_sub(date_line.chars().count()) / 2;
|
||||
lines.push(Line::from(vec![
|
||||
@@ -485,13 +485,13 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
last_sender = None; // Сбрасываем отправителя при смене дня
|
||||
}
|
||||
|
||||
let sender_name = if msg.is_outgoing {
|
||||
let sender_name = if msg.is_outgoing() {
|
||||
"Вы".to_string()
|
||||
} 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(¤t_sender);
|
||||
@@ -502,7 +502,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
lines.push(Line::from(""));
|
||||
}
|
||||
|
||||
let sender_style = if msg.is_outgoing {
|
||||
let sender_style = if msg.is_outgoing() {
|
||||
Style::default()
|
||||
.fg(Color::Green)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
@@ -512,7 +512,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
.add_modifier(Modifier::BOLD)
|
||||
};
|
||||
|
||||
if msg.is_outgoing {
|
||||
if msg.is_outgoing() {
|
||||
// Заголовок "Вы" справа
|
||||
let header_text = format!("{} ────────────────", sender_name);
|
||||
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
|
||||
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 или жёлтый если выбрано)
|
||||
let msg_color = if is_selected {
|
||||
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)
|
||||
} else {
|
||||
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();
|
||||
|
||||
// Отображаем forward если есть
|
||||
if let Some(forward) = &msg.forward_from {
|
||||
if let Some(forward) = msg.forward_from() {
|
||||
let forward_line = format!("↪ Переслано от {}", forward.sender_name);
|
||||
let forward_len = forward_line.chars().count();
|
||||
|
||||
if msg.is_outgoing {
|
||||
if msg.is_outgoing() {
|
||||
// Forward справа для исходящих
|
||||
let padding = content_width.saturating_sub(forward_len + 1);
|
||||
lines.push(Line::from(vec![
|
||||
@@ -571,7 +571,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
}
|
||||
|
||||
// Отображаем 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 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_len = reply_line.chars().count();
|
||||
|
||||
if msg.is_outgoing {
|
||||
if msg.is_outgoing() {
|
||||
// Reply справа для исходящих
|
||||
let padding = content_width.saturating_sub(reply_len + 1);
|
||||
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 ✎ ✓✓)"
|
||||
let read_mark = if msg.is_read { "✓✓" } else { "✓" };
|
||||
let edit_mark = if msg.edit_date > 0 { "✎ " } else { "" };
|
||||
let read_mark = if msg.is_read() { "✓✓" } else { "✓" };
|
||||
let edit_mark = if msg.is_edited() { "✎ " } else { "" };
|
||||
let time_mark = format!("({} {}{})", time, edit_mark, read_mark);
|
||||
let time_mark_len = time_mark.chars().count() + 1; // +1 для пробела
|
||||
|
||||
// Максимальная ширина для текста сообщения (оставляем место для time_mark и маркера)
|
||||
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();
|
||||
|
||||
for (i, wrapped) in wrapped_lines.into_iter().enumerate() {
|
||||
@@ -616,7 +616,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
|
||||
// Получаем entities для этой строки
|
||||
let line_entities = adjust_entities_for_substring(
|
||||
&msg.entities,
|
||||
msg.entities(),
|
||||
wrapped.start_offset,
|
||||
line_len,
|
||||
);
|
||||
@@ -662,21 +662,21 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
}
|
||||
} else {
|
||||
// Входящие: слева, формат "(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_prefix_len = time_str.chars().count() + 2; // " (HH:MM) "
|
||||
|
||||
// Максимальная ширина для текста
|
||||
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() {
|
||||
let line_len = wrapped.text.chars().count();
|
||||
|
||||
// Получаем entities для этой строки
|
||||
let line_entities = adjust_entities_for_substring(
|
||||
&msg.entities,
|
||||
msg.entities(),
|
||||
wrapped.start_offset,
|
||||
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![];
|
||||
|
||||
for reaction in &msg.reactions {
|
||||
for reaction in &msg.reactions() {
|
||||
if !reaction_spans.is_empty() {
|
||||
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
|
||||
.iter()
|
||||
@@ -815,8 +815,8 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
let forward_preview = app
|
||||
.get_forwarding_message()
|
||||
.map(|m| {
|
||||
let text_preview: String = m.content.chars().take(40).collect();
|
||||
let ellipsis = if m.content.chars().count() > 40 {
|
||||
let text_preview: String = m.text().chars().take(40).collect();
|
||||
let ellipsis = if m.text().chars().count() > 40 {
|
||||
"..."
|
||||
} else {
|
||||
""
|
||||
@@ -875,10 +875,10 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
let sender = if m.is_outgoing {
|
||||
"Вы"
|
||||
} else {
|
||||
&m.sender_name
|
||||
m.sender_name()
|
||||
};
|
||||
let text_preview: String = m.content.chars().take(30).collect();
|
||||
let ellipsis = if m.content.chars().count() > 30 {
|
||||
let text_preview: String = m.text().chars().take(30).collect();
|
||||
let ellipsis = if m.text().chars().count() > 30 {
|
||||
"..."
|
||||
} else {
|
||||
""
|
||||
@@ -1056,15 +1056,15 @@ fn render_search_mode(f: &mut Frame, area: Rect, app: &App) {
|
||||
|
||||
// Маркер выбора, имя и дата
|
||||
let marker = if is_selected { "▶ " } else { " " };
|
||||
let sender_color = if msg.is_outgoing {
|
||||
let sender_color = if msg.is_outgoing() {
|
||||
Color::Green
|
||||
} else {
|
||||
Color::Cyan
|
||||
};
|
||||
let sender_name = if msg.is_outgoing {
|
||||
let sender_name = if msg.is_outgoing() {
|
||||
"Вы".to_string()
|
||||
} else {
|
||||
msg.sender_name.clone()
|
||||
msg.sender_name().to_string()
|
||||
};
|
||||
|
||||
lines.push(Line::from(vec![
|
||||
@@ -1081,7 +1081,7 @@ fn render_search_mode(f: &mut Frame, area: Rect, app: &App) {
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::styled(
|
||||
format!("({})", crate::utils::format_datetime(msg.date)),
|
||||
format!("({})", crate::utils::format_datetime(msg.date())),
|
||||
Style::default().fg(Color::Gray),
|
||||
),
|
||||
]));
|
||||
@@ -1093,7 +1093,7 @@ fn render_search_mode(f: &mut Frame, area: Rect, app: &App) {
|
||||
Color::White
|
||||
};
|
||||
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();
|
||||
|
||||
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 sender_color = if msg.is_outgoing {
|
||||
let sender_color = if msg.is_outgoing() {
|
||||
Color::Green
|
||||
} else {
|
||||
Color::Cyan
|
||||
};
|
||||
let sender_name = if msg.is_outgoing {
|
||||
let sender_name = if msg.is_outgoing() {
|
||||
"Вы".to_string()
|
||||
} else {
|
||||
msg.sender_name.clone()
|
||||
msg.sender_name().to_string()
|
||||
};
|
||||
|
||||
lines.push(Line::from(vec![
|
||||
@@ -1247,7 +1247,7 @@ fn render_pinned_mode(f: &mut Frame, area: Rect, app: &App) {
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::styled(
|
||||
format!("({})", crate::utils::format_datetime(msg.date)),
|
||||
format!("({})", crate::utils::format_datetime(msg.date())),
|
||||
Style::default().fg(Color::Gray),
|
||||
),
|
||||
]));
|
||||
@@ -1259,7 +1259,7 @@ fn render_pinned_mode(f: &mut Frame, area: Rect, app: &App) {
|
||||
Color::White
|
||||
};
|
||||
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();
|
||||
|
||||
for wrapped_line in wrapped.into_iter().take(3) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ fn test_delete_multiple_messages() {
|
||||
let messages = client.get_messages(123);
|
||||
assert_eq!(messages.len(), 1);
|
||||
assert_eq!(messages[0].id, msg2_id);
|
||||
assert_eq!(messages[0].content, "Message 2");
|
||||
assert_eq!(messages[0].content.text(), "Message 2");
|
||||
}
|
||||
|
||||
/// Test: Удаление только своих сообщений (проверка через can_be_deleted_for_all_users)
|
||||
@@ -145,5 +145,5 @@ fn test_cancel_delete_keeps_message() {
|
||||
// Сообщение на месте
|
||||
let messages = client.get_messages(123);
|
||||
assert_eq!(messages[0].id, msg_id);
|
||||
assert_eq!(messages[0].content, "Keep me");
|
||||
assert_eq!(messages[0].content.text(), "Keep me");
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ fn test_edit_message_changes_text() {
|
||||
// Проверяем что текст сообщения изменился
|
||||
let messages = client.get_messages(123);
|
||||
assert_eq!(messages.len(), 1);
|
||||
assert_eq!(messages[0].content, "Edited text");
|
||||
assert_eq!(messages[0].content.text(), "Edited text");
|
||||
}
|
||||
|
||||
/// Test: Редактирование устанавливает edit_date
|
||||
@@ -97,7 +97,7 @@ fn test_multiple_edits_of_same_message() {
|
||||
// Проверяем что сообщение содержит последнюю версию
|
||||
let messages = client.get_messages(123);
|
||||
assert_eq!(messages.len(), 1);
|
||||
assert_eq!(messages[0].content, "Final version");
|
||||
assert_eq!(messages[0].content.text(), "Final version");
|
||||
}
|
||||
|
||||
/// Test: Редактирование несуществующего сообщения (ничего не происходит)
|
||||
@@ -129,21 +129,21 @@ fn test_edit_history_tracking() {
|
||||
|
||||
// Сохраняем original
|
||||
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());
|
||||
|
||||
// Проверяем что изменилось
|
||||
let messages_edited = client.get_messages(123);
|
||||
assert_eq!(messages_edited[0].content, "Edited");
|
||||
assert_eq!(messages_edited[0].content.text(), "Edited");
|
||||
|
||||
// Можем "отменить" редактирование вернув original
|
||||
client.edit_message(123, msg_id, original);
|
||||
|
||||
// Проверяем что вернулось
|
||||
let messages_restored = client.get_messages(123);
|
||||
assert_eq!(messages_restored[0].content, "Original");
|
||||
assert_eq!(messages_restored[0].content.text(), "Original");
|
||||
|
||||
// История показывает 2 редактирования
|
||||
assert_eq!(client.edited_messages().len(), 2);
|
||||
|
||||
@@ -156,9 +156,9 @@ impl FakeTdClient {
|
||||
|
||||
// Обновляем сообщение в списке
|
||||
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;
|
||||
if let Some(msg) = messages.iter_mut().find(|m| m.id() == message_id) {
|
||||
msg.content.text = new_text;
|
||||
msg.metadata.edit_date = msg.metadata.date + 60;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,22 +188,22 @@ impl TestMessageBuilder {
|
||||
}
|
||||
|
||||
pub fn build(self) -> MessageInfo {
|
||||
MessageInfo {
|
||||
id: MessageId::new(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,
|
||||
}
|
||||
MessageInfo::new(
|
||||
MessageId::new(self.id),
|
||||
self.sender_name,
|
||||
self.is_outgoing,
|
||||
self.content,
|
||||
self.entities,
|
||||
self.date,
|
||||
self.edit_date,
|
||||
self.is_read,
|
||||
self.can_be_edited,
|
||||
self.can_be_deleted_only_for_self,
|
||||
self.can_be_deleted_for_all_users,
|
||||
self.reply_to,
|
||||
self.forward_from,
|
||||
self.reactions,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -223,6 +223,6 @@ fn test_load_older_messages_on_scroll_up() {
|
||||
|
||||
// Теперь должно быть 15 сообщений
|
||||
assert_eq!(client.get_messages(123).len(), 15);
|
||||
assert_eq!(client.get_messages(123)[0].content, "Msg 81");
|
||||
assert_eq!(client.get_messages(123)[14].content, "Msg 100");
|
||||
assert_eq!(client.get_messages(123)[0].content.text(), "Msg 81");
|
||||
assert_eq!(client.get_messages(123)[14].content.text(), "Msg 100");
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ fn test_reply_creates_message_with_reply_to() {
|
||||
let messages = client.get_messages(123);
|
||||
assert_eq!(messages.len(), 2);
|
||||
assert_eq!(messages[1].id, reply_id);
|
||||
assert_eq!(messages[1].content, "Answer!");
|
||||
assert_eq!(messages[1].content.text(), "Answer!");
|
||||
}
|
||||
|
||||
/// Test: Reply отображает превью оригинального сообщения
|
||||
@@ -76,7 +76,7 @@ fn test_cancel_reply_sends_without_reply_to() {
|
||||
assert_eq!(client.sent_messages()[0].reply_to, None);
|
||||
|
||||
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
|
||||
|
||||
@@ -99,12 +99,12 @@ fn test_search_messages_in_chat() {
|
||||
let messages = client.get_messages(123);
|
||||
let found: Vec<_> = messages
|
||||
.iter()
|
||||
.filter(|m| m.content.to_lowercase().contains(&query))
|
||||
.filter(|m| m.text().to_lowercase().contains(&query))
|
||||
.collect();
|
||||
|
||||
assert_eq!(found.len(), 2);
|
||||
assert_eq!(found[0].content, "Hello world");
|
||||
assert_eq!(found[1].content, "Hello again");
|
||||
assert_eq!(found[0].text(), "Hello world");
|
||||
assert_eq!(found[1].text(), "Hello again");
|
||||
}
|
||||
|
||||
/// Test: Навигация по результатам поиска (n/N)
|
||||
@@ -124,7 +124,7 @@ fn test_navigate_search_results() {
|
||||
let results: Vec<_> = messages
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, m)| m.content.to_lowercase().contains(&query))
|
||||
.filter(|(_, m)| m.text().to_lowercase().contains(&query))
|
||||
.collect();
|
||||
|
||||
assert_eq!(results.len(), 3);
|
||||
@@ -135,17 +135,17 @@ fn test_navigate_search_results() {
|
||||
// n - следующий результат
|
||||
current_index = (current_index + 1) % results.len();
|
||||
assert_eq!(current_index, 1);
|
||||
assert_eq!(results[current_index].1.content, "Second match");
|
||||
assert_eq!(results[current_index].1.text(), "Second match");
|
||||
|
||||
// n - ещё один
|
||||
current_index = (current_index + 1) % results.len();
|
||||
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 к первому
|
||||
current_index = (current_index + 1) % results.len();
|
||||
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)
|
||||
current_index = if current_index == 0 {
|
||||
@@ -154,7 +154,7 @@ fn test_navigate_search_results() {
|
||||
current_index - 1
|
||||
};
|
||||
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)
|
||||
@@ -173,7 +173,7 @@ fn test_search_case_insensitive() {
|
||||
let messages = client.get_messages(123);
|
||||
let found: Vec<_> = messages
|
||||
.iter()
|
||||
.filter(|m| m.content.to_lowercase().contains(&query))
|
||||
.filter(|m| m.text().to_lowercase().contains(&query))
|
||||
.collect();
|
||||
|
||||
// Все 3 варианта должны найтись
|
||||
@@ -195,7 +195,7 @@ fn test_search_no_results() {
|
||||
let messages = client.get_messages(123);
|
||||
let found: Vec<_> = messages
|
||||
.iter()
|
||||
.filter(|m| m.content.to_lowercase().contains(&query))
|
||||
.filter(|m| m.text().to_lowercase().contains(&query))
|
||||
.collect();
|
||||
|
||||
assert_eq!(found.len(), 0);
|
||||
|
||||
@@ -25,7 +25,7 @@ fn test_send_text_message() {
|
||||
let messages = client.get_messages(123);
|
||||
assert_eq!(messages.len(), 1);
|
||||
assert_eq!(messages[0].id, msg_id);
|
||||
assert_eq!(messages[0].content, "Hello, Mom!");
|
||||
assert_eq!(messages.text(), "Hello, Mom!");
|
||||
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[1].id, msg2_id);
|
||||
assert_eq!(messages[2].id, msg3_id);
|
||||
assert_eq!(messages[0].content, "Message 1");
|
||||
assert_eq!(messages[1].content, "Message 2");
|
||||
assert_eq!(messages[2].content, "Message 3");
|
||||
assert_eq!(messages.text(), "Message 1");
|
||||
assert_eq!(messages.text(), "Message 2");
|
||||
assert_eq!(messages.text(), "Message 3");
|
||||
}
|
||||
|
||||
/// Test: Отправка пустого сообщения (должно быть игнорировано на уровне App)
|
||||
@@ -74,7 +74,7 @@ fn test_send_empty_message_technical() {
|
||||
let messages = client.get_messages(123);
|
||||
assert_eq!(messages.len(), 1);
|
||||
assert_eq!(messages[0].id, msg_id);
|
||||
assert_eq!(messages[0].content, "");
|
||||
assert_eq!(messages.text(), "");
|
||||
}
|
||||
|
||||
/// Test: Отправка сообщения с форматированием (markdown сущности)
|
||||
@@ -89,7 +89,7 @@ fn test_send_message_with_markdown() {
|
||||
// Проверяем что текст сохранился как есть (парсинг markdown - отдельная логика)
|
||||
let messages = client.get_messages(123);
|
||||
assert_eq!(messages.len(), 1);
|
||||
assert_eq!(messages[0].content, text);
|
||||
assert_eq!(messages.text(), text);
|
||||
}
|
||||
|
||||
/// Test: Отправка сообщения в разные чаты
|
||||
@@ -112,12 +112,12 @@ fn test_send_messages_to_different_chats() {
|
||||
// Проверяем что сообщения распределены по чатам
|
||||
let chat123_messages = client.get_messages(123);
|
||||
assert_eq!(chat123_messages.len(), 2);
|
||||
assert_eq!(chat123_messages[0].content, "Hello Mom");
|
||||
assert_eq!(chat123_messages[1].content, "How are you?");
|
||||
assert_eq!(chat123_messages.text(), "Hello Mom");
|
||||
assert_eq!(chat123_messages.text(), "How are you?");
|
||||
|
||||
let chat456_messages = client.get_messages(456);
|
||||
assert_eq!(chat456_messages.len(), 1);
|
||||
assert_eq!(chat456_messages[0].content, "Hello Boss");
|
||||
assert_eq!(chat456_messages.text(), "Hello Boss");
|
||||
}
|
||||
|
||||
/// Test: Новое сообщение появляется в реальном времени (симуляция)
|
||||
@@ -141,6 +141,6 @@ fn test_receive_incoming_message() {
|
||||
assert_eq!(messages.len(), 2);
|
||||
assert_eq!(messages[0].is_outgoing, true); // Наше сообщение
|
||||
assert_eq!(messages[1].is_outgoing, false); // Входящее
|
||||
assert_eq!(messages[1].content, "Hey there!");
|
||||
assert_eq!(messages.text(), "Hey there!");
|
||||
assert_eq!(messages[1].sender_name, "Alice");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user