Files
telegram-tui/tests/modals.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

199 lines
6.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Modals UI snapshot tests
mod helpers;
use helpers::app_builder::TestAppBuilder;
use tele_tui::tdlib::TdClientTrait;
use helpers::snapshot_utils::{buffer_to_string, render_to_buffer};
use helpers::test_data::{
create_test_chat, create_test_profile, TestChatBuilder, TestMessageBuilder,
};
use insta::assert_snapshot;
#[test]
fn snapshot_delete_confirmation_modal() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("Delete me", 1).outgoing().build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.selected_chat(123)
.delete_confirmation(1)
.build();
let buffer = render_to_buffer(80, 24, |f| {
tele_tui::ui::messages::render(f, f.area(), &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("delete_confirmation_modal", output);
}
#[test]
fn snapshot_emoji_picker_default() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("React to this", 1).build();
let reactions = vec!["👍".to_string(), "👎".to_string(), "❤️".to_string(), "🔥".to_string(), "😊".to_string(), "😢".to_string(), "😮".to_string(), "🎉".to_string()];
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.selected_chat(123)
.reaction_picker(1, reactions)
.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!("emoji_picker_default", output);
}
#[test]
fn snapshot_emoji_picker_with_selection() {
let chat = create_test_chat("Mom", 123);
let message = TestMessageBuilder::new("React to this", 1).build();
let reactions = vec!["👍".to_string(), "👎".to_string(), "❤️".to_string(), "🔥".to_string(), "😊".to_string(), "😢".to_string(), "😮".to_string(), "🎉".to_string()];
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_message(123, message)
.selected_chat(123)
.reaction_picker(1, reactions)
.build();
// Выбираем 5-ю реакцию (индекс 4)
if let tele_tui::app::ChatState::ReactionPicker { selected_index, .. } = &mut app.chat_state {
*selected_index = 4;
}
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!("emoji_picker_with_selection", output);
}
#[test]
fn snapshot_profile_personal_chat() {
let chat = create_test_chat("Alice", 123);
let profile = create_test_profile("Alice", 123);
let mut app = TestAppBuilder::new()
.with_chat(chat)
.selected_chat(123)
.profile_mode(profile)
.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!("profile_personal_chat", output);
}
#[test]
fn snapshot_profile_group_chat() {
let chat = TestChatBuilder::new("Work Group", 456).build();
let mut profile = create_test_profile("Work Group", 456);
profile.is_group = true;
profile.chat_type = "Группа".to_string();
profile.member_count = Some(25);
profile.description = Some("Work discussion group".to_string());
let mut app = TestAppBuilder::new()
.with_chat(chat)
.selected_chat(456)
.profile_mode(profile)
.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!("profile_group_chat", output);
}
#[test]
fn snapshot_pinned_message() {
let chat = create_test_chat("Mom", 123);
let message1 = TestMessageBuilder::new("Regular message", 1).build();
let pinned_msg = TestMessageBuilder::new("Important pinned message!", 2).build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_messages(123, vec![message1])
.selected_chat(123)
.build();
// Устанавливаем закреплённое сообщение
app.td_client.set_current_pinned_message(Some(pinned_msg));
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!("pinned_message", output);
}
#[test]
fn snapshot_search_in_chat() {
let chat = create_test_chat("Mom", 123);
let msg1 = TestMessageBuilder::new("Hello world", 1).build();
let msg2 = TestMessageBuilder::new("World is beautiful", 2).build();
let msg3 = TestMessageBuilder::new("Beautiful day", 3).build();
let mut app = TestAppBuilder::new()
.with_chat(chat)
.with_messages(123, vec![msg1.clone(), msg2.clone(), msg3])
.selected_chat(123)
.message_search("world")
.build();
// Устанавливаем результаты поиска
if let tele_tui::app::ChatState::SearchInChat { results, selected_index, .. } = &mut app.chat_state {
*results = vec![msg1, msg2];
*selected_index = 0;
}
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!("search_in_chat", output);
}
#[test]
fn snapshot_forward_mode() {
let chat1 = create_test_chat("Mom", 123);
let chat2 = create_test_chat("Dad", 456);
let chat3 = create_test_chat("Work Group", 789);
let message = TestMessageBuilder::new("Forward this message", 1).build();
let mut app = TestAppBuilder::new()
.with_chats(vec![chat1.clone(), chat2, chat3])
.with_message(123, message)
.selected_chat(123)
.forward_mode(1)
.build();
let buffer = render_to_buffer(80, 24, |f| {
// В forward mode показывается chat_list для выбора чата
tele_tui::ui::chat_list::render(f, f.area(), &mut app);
});
let output = buffer_to_string(&buffer);
assert_snapshot!("forward_mode", output);
}