Implement complete trait-based dependency injection pattern for TdClient to enable testing with FakeTdClient mock. Fix critical stack overflow bugs caused by infinite recursion in trait implementations. Breaking Changes: - App is now generic: App<T: TdClientTrait = TdClient> - All UI and input handlers are generic over TdClientTrait - TdClient methods now accessed through trait interface New Files: - src/tdlib/trait.rs: TdClientTrait definition with 40+ methods - src/tdlib/client_impl.rs: TdClientTrait impl for TdClient - tests/helpers/fake_tdclient_impl.rs: TdClientTrait impl for FakeTdClient Critical Fixes: - Fix stack overflow in send_message, edit_message, delete_messages - Fix stack overflow in forward_messages, current_chat_messages - Fix stack overflow in current_pinned_message - All methods now call message_manager directly to avoid recursion Testing: - FakeTdClient supports configurable auth_state for auth screen tests - Added pinned message support in FakeTdClient - All 196+ tests passing (188 tests + 8 benchmarks) Dependencies: - Added async-trait = "0.1" Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
199 lines
6.2 KiB
Rust
199 lines
6.2 KiB
Rust
// 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 app = TestAppBuilder::new()
|
||
.with_chat(chat)
|
||
.with_message(123, message)
|
||
.selected_chat(123)
|
||
.delete_confirmation(1)
|
||
.build();
|
||
|
||
let buffer = render_to_buffer(80, 24, |f| {
|
||
tele_tui::ui::messages::render(f, f.area(), &app);
|
||
});
|
||
|
||
let output = buffer_to_string(&buffer);
|
||
assert_snapshot!("delete_confirmation_modal", output);
|
||
}
|
||
|
||
#[test]
|
||
fn snapshot_emoji_picker_default() {
|
||
let chat = create_test_chat("Mom", 123);
|
||
let message = TestMessageBuilder::new("React to this", 1).build();
|
||
|
||
let reactions = vec!["👍".to_string(), "👎".to_string(), "❤️".to_string(), "🔥".to_string(), "😊".to_string(), "😢".to_string(), "😮".to_string(), "🎉".to_string()];
|
||
|
||
let 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(), &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(), &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 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(), &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 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(), &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(), &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(), &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);
|
||
}
|