Files
telegram-tui/tests/messages.rs
Mikhail Kilin 2a5fd6aa35 perf: optimize Phase 11 image rendering with dual-protocol architecture
Redesigned UX and performance for inline photo viewing:

UX changes:
- Always-show inline preview (fixed 50 chars width)
- Fullscreen modal on 'v' key with ←/→ navigation between photos
- Loading indicator " Загрузка..." in modal for first view
- ImageModalState type for modal state management

Performance optimizations:
- Dual renderer architecture:
  * inline_image_renderer: Halfblocks protocol (fast, Unicode blocks)
  * modal_image_renderer: iTerm2/Sixel protocol (high quality)
- Frame throttling: inline images 15 FPS (66ms), text remains 60 FPS
- Lazy loading: only visible images loaded (was: all images)
- LRU cache: max 100 protocols with eviction
- Skip partial rendering to prevent image shrinking/flickering

Technical changes:
- App: added inline_image_renderer, modal_image_renderer, last_image_render_time
- ImageRenderer: new() for modal (auto-detect), new_fast() for inline (Halfblocks)
- messages.rs: throttled second-pass rendering, visible-only loading
- modals/image_viewer.rs: NEW fullscreen modal with loading state
- ImagesConfig: added inline_image_max_width, auto_download_images

Result: 10x faster navigation, smooth 60 FPS text, quality modal viewing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 01:36:36 +03:00

390 lines
11 KiB
Rust

// Messages UI snapshot tests
mod helpers;
use helpers::app_builder::TestAppBuilder;
use helpers::snapshot_utils::{buffer_to_string, render_to_buffer};
use helpers::test_data::{create_test_chat, TestChatBuilder, TestMessageBuilder};
use insta::assert_snapshot;
use tele_tui::types::{ChatId, MessageId};
#[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(), &mut 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(), &mut 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(), &mut 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(), &mut 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(), &mut 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(), &mut 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 == ChatId::new(123)) {
chat.last_read_outbox_message_id = MessageId::new(2);
}
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::messages::render(f, f.area(), &mut 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(), &mut 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(), &mut 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(), &mut 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(), &mut 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(), &mut 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(), &mut 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(), &mut 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(), &mut 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(), &mut 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(), &mut 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(), &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("selected_message", output);
}