refactor: implement trait-based DI for TdClient and fix stack overflow
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>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
use crate::app::App;
|
||||
use crate::tdlib::TdClientTrait;
|
||||
use crate::message_grouping::{group_messages, MessageGroup};
|
||||
use crate::ui::components;
|
||||
use ratatui::{
|
||||
@@ -113,7 +114,7 @@ fn wrap_text_with_offsets(text: &str, max_width: usize) -> Vec<WrappedLine> {
|
||||
result
|
||||
}
|
||||
|
||||
pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
pub fn render<T: TdClientTrait>(f: &mut Frame, area: Rect, app: &App<T>) {
|
||||
// Режим профиля
|
||||
if app.is_profile_mode() {
|
||||
if let Some(profile) = app.get_profile_info() {
|
||||
@@ -213,7 +214,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
f.render_widget(header, message_chunks[0]);
|
||||
|
||||
// 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.text().chars().take(40).collect();
|
||||
let ellipsis = if pinned_msg.text().chars().count() > 40 {
|
||||
"..."
|
||||
@@ -251,7 +252,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
let mut selected_msg_line: Option<usize> = None;
|
||||
|
||||
// Используем message_grouping для группировки сообщений
|
||||
let grouped = group_messages(app.td_client.current_chat_messages());
|
||||
let grouped = group_messages(&app.td_client.current_chat_messages());
|
||||
let mut is_first_date = true;
|
||||
let mut is_first_sender = true;
|
||||
|
||||
@@ -357,9 +358,11 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
// Режим выбора сообщения - подсказка зависит от возможностей
|
||||
let selected_msg = app.get_selected_message();
|
||||
let can_edit = selected_msg
|
||||
.as_ref()
|
||||
.map(|m| m.can_be_edited() && m.is_outgoing())
|
||||
.unwrap_or(false);
|
||||
let can_delete = selected_msg
|
||||
.as_ref()
|
||||
.map(|m| m.can_be_deleted_only_for_self() || m.can_be_deleted_for_all_users())
|
||||
.unwrap_or(false);
|
||||
|
||||
@@ -501,7 +504,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
}
|
||||
|
||||
/// Рендерит режим поиска по сообщениям
|
||||
fn render_search_mode(f: &mut Frame, area: Rect, app: &App) {
|
||||
fn render_search_mode<T: TdClientTrait>(f: &mut Frame, area: Rect, app: &App<T>) {
|
||||
// Извлекаем данные из ChatState
|
||||
let (query, results, selected_index) =
|
||||
if let crate::app::ChatState::SearchInChat {
|
||||
@@ -696,7 +699,7 @@ fn render_search_mode(f: &mut Frame, area: Rect, app: &App) {
|
||||
}
|
||||
|
||||
/// Рендерит режим просмотра закреплённых сообщений
|
||||
fn render_pinned_mode(f: &mut Frame, area: Rect, app: &App) {
|
||||
fn render_pinned_mode<T: TdClientTrait>(f: &mut Frame, area: Rect, app: &App<T>) {
|
||||
// Извлекаем данные из ChatState
|
||||
let (messages, selected_index) = if let crate::app::ChatState::PinnedMessages {
|
||||
messages,
|
||||
|
||||
Reference in New Issue
Block a user