diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index e4acc8b..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -name: Bug Report -about: Сообщить о проблеме или баге -title: '[BUG] ' -labels: bug -assignees: '' ---- - -## Описание бага -Четкое и краткое описание проблемы. - -## Шаги для воспроизведения -1. Запустить '...' -2. Нажать на '...' -3. Прокрутить вниз до '...' -4. Увидеть ошибку - -## Ожидаемое поведение -Что должно было произойти. - -## Фактическое поведение -Что произошло на самом деле. - -## Скриншоты -Если применимо, добавьте скриншоты для демонстрации проблемы. - -## Окружение -- **ОС**: [например, macOS 14.0, Ubuntu 22.04, Windows 11] -- **Rust версия**: [вывод `rustc --version`] -- **tele-tui версия**: [вывод `cargo pkgid`] -- **Размер терминала**: [например, 100x30] - -## Логи -Если есть логи или сообщения об ошибках, вставьте их сюда: -``` -вставьте логи здесь -``` - -## Дополнительный контекст -Любая другая информация, которая может помочь в решении проблемы. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index c5702cd..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: Feature Request -about: Предложить новую функцию или улучшение -title: '[FEATURE] ' -labels: enhancement -assignees: '' ---- - -## Связано с проблемой? -Есть ли проблема, которую это решит? Например: "Меня расстраивает, что [...]" - -## Описание решения -Четкое и краткое описание того, что вы хотите. - -## Альтернативы -Какие альтернативные решения или функции вы рассматривали? - -## Примеры использования -Как эта функция будет использоваться? Приведите примеры: - -1. Пользователь делает X -2. Система делает Y -3. Результат: Z - -## Приоритет -- [ ] Критичная функция — без неё приложение малополезно -- [ ] Важная функция — значительно улучшит UX -- [ ] Nice to have — было бы удобно - -## Проверка roadmap -- [ ] Я проверил [ROADMAP.md](../ROADMAP.md) и этой функции там нет - -## Дополнительный контекст -Скриншоты, ссылки на похожие реализации в других приложениях, и т.д. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index caaf515..0000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,51 +0,0 @@ -## Описание - -Краткое описание изменений в этом PR. - -## Тип изменений - -- [ ] Bug fix (исправление бага) -- [ ] New feature (новая функция) -- [ ] Breaking change (изменение, ломающее обратную совместимость) -- [ ] Refactoring (рефакторинг без изменения функциональности) -- [ ] Documentation (изменения в документации) -- [ ] Performance improvement (улучшение производительности) - -## Связанные Issue - -Fixes #(номер issue) - -## Как протестировано? - -Опишите тесты, которые вы провели: - -- [ ] Тест A -- [ ] Тест B -- [ ] Тест C - -## Сценарии тестирования - -Подробные шаги для проверки изменений: - -1. Запустить `cargo run` -2. Сделать X -3. Убедиться, что Y - -## Чеклист - -- [ ] Мой код следует стилю проекта -- [ ] Я запустил `cargo fmt` -- [ ] Я запустил `cargo clippy` и исправил warnings -- [ ] Код компилируется без ошибок (`cargo build`) -- [ ] Я протестировал изменения вручную -- [ ] Я обновил документацию (если необходимо) -- [ ] Я добавил тесты (если применимо) -- [ ] Все существующие тесты проходят - -## Скриншоты (если применимо) - -Добавьте скриншоты для демонстрации UI изменений. - -## Дополнительные заметки - -Любая дополнительная информация для ревьюверов. diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 712a3c5..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,66 +0,0 @@ -# Changelog - -Все значительные изменения в этом проекте будут документированы в этом файле. - -Формат основан на [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -и этот проект придерживается [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.1.0] - 2024-12-XX - -### Добавлено - -#### Базовая функциональность -- TDLib интеграция с авторизацией (телефон + код + 2FA) -- Отображение списка чатов с поддержкой папок -- Загрузка и отображение истории сообщений -- Отправка текстовых сообщений -- Vim-style навигация (hjkl) с поддержкой русской раскладки (ролд) -- Поиск по чатам (Ctrl+S) -- Поиск внутри чата (Ctrl+F) - -#### Сообщения -- Группировка по дате и отправителю -- Markdown форматирование (жирный, курсив, подчёркивание, зачёркивание, код, спойлеры) -- Редактирование сообщений -- Удаление сообщений с подтверждением -- Reply на сообщения -- Forward сообщений -- Копирование в системный буфер обмена -- Реакции на сообщения с emoji picker - -#### UI/UX -- Индикаторы: онлайн-статус (●), прочитанность (✓/✓✓), редактирование (✎) -- Иконки: 📌 закреплённые чаты, 🔇 замьюченные, @ упоминания -- Typing indicator ("печатает...") -- Закреплённые сообщения -- Профиль пользователя/чата -- Черновики с автосохранением -- Динамический инпут (расширение до 10 строк) -- Блочный курсор с навигацией -- Состояние сети в футере - -#### Конфигурация -- TOML конфигурация (~/.config/tele-tui/config.toml) -- Настройка часового пояса -- Настройка цветовой схемы -- Приоритетная загрузка credentials из XDG config dir - -#### Оптимизации -- 60 FPS рендеринг -- LRU кеширование пользователей (лимит 500) -- Lazy loading имён пользователей -- Лимиты памяти (500 сообщений на чат, 200 чатов) -- Graceful shutdown - -### Изменено -- Время отображается с учётом настроенного timezone - -### Исправлено -- Корректная обработка TDLib updates в отдельном потоке -- Правильное выравнивание для длинных сообщений -- Приоритет обработки input для модалок - -[Unreleased]: https://github.com/your-username/tele-tui/compare/v0.1.0...HEAD -[0.1.0]: https://github.com/your-username/tele-tui/releases/tag/v0.1.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index b452d5c..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,125 +0,0 @@ -# Contributing to tele-tui - -Спасибо за интерес к проекту! Мы рады любому вкладу. - -## Как помочь проекту - -### Сообщить о баге - -1. Проверьте, нет ли уже такого issue в [Issues](https://github.com/your-username/tele-tui/issues) -2. Создайте новый issue с описанием: - - Шаги для воспроизведения - - Ожидаемое поведение - - Фактическое поведение - - Версия ОС и Rust - - Логи (если есть) - -### Предложить новую фичу - -1. Проверьте [ROADMAP.md](ROADMAP.md) — возможно, эта фича уже запланирована -2. Создайте issue с меткой `enhancement` -3. Опишите: - - Зачем нужна эта фича - - Как она должна работать - - Примеры использования - -### Внести код - -1. **Fork** репозитория -2. Создайте **feature branch**: `git checkout -b feature/amazing-feature` -3. Прочитайте [DEVELOPMENT.md](DEVELOPMENT.md) для понимания процесса разработки -4. Внесите изменения -5. Протестируйте локально -6. Commit: `git commit -m 'Add amazing feature'` -7. Push: `git push origin feature/amazing-feature` -8. Создайте **Pull Request** - -## Правила кода - -### Стиль кода - -- Используйте `cargo fmt` перед коммитом -- Следуйте [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/) -- Добавляйте комментарии для сложной логики - -### Структура коммитов - -``` -: <краткое описание> - -<подробное описание (опционально)> -``` - -Типы: -- `feat`: новая фича -- `fix`: исправление бага -- `refactor`: рефакторинг без изменения функциональности -- `docs`: изменения в документации -- `style`: форматирование, отступы -- `test`: добавление тестов -- `chore`: обновление зависимостей, конфигурации - -Примеры: -``` -feat: add emoji reactions to messages - -fix: correct timezone offset calculation - -docs: update installation instructions -``` - -### Тестирование - -- Протестируйте вручную все изменения -- Опишите сценарии тестирования в PR -- Убедитесь, что `cargo build` проходит без ошибок -- Убедитесь, что `cargo fmt` и `cargo clippy` не дают предупреждений - -## Процесс Review - -1. Maintainer проверит ваш PR -2. Возможны комментарии и запросы на изменения -3. После одобрения PR будет смержен -4. Ваш вклад появится в следующем релизе - -## Архитектура проекта - -Перед началом работы рекомендуем ознакомиться: - -- [REQUIREMENTS.md](REQUIREMENTS.md) — функциональные требования -- [CONTEXT.md](CONTEXT.md) — текущий статус и архитектурные решения -- [ROADMAP.md](ROADMAP.md) — план развития - -### Структура кода - -``` -src/ -├── main.rs # Event loop, инициализация -├── config.rs # Конфигурация -├── app/ # Состояние приложения -├── ui/ # Отрисовка UI -├── input/ # Обработка ввода -├── utils.rs # Утилиты -└── tdlib/ # TDLib интеграция -``` - -### Ключевые принципы - -1. **Неблокирующий UI**: TDLib updates в отдельном потоке -2. **Оптимизация памяти**: LRU кеши, лимиты на коллекции -3. **Vim-style навигация**: консистентные хоткеи -4. **Graceful degradation**: fallback для отсутствующих данных - -## Code of Conduct - -- Будьте вежливы и уважительны -- Конструктивная критика приветствуется -- Фокус на технических аспектах - -## Вопросы? - -Создайте issue с меткой `question` или свяжитесь с maintainers. - -## Лицензия - -Внося код в этот проект, вы соглашаетесь с лицензией [MIT](LICENSE). diff --git a/FAQ.md b/FAQ.md deleted file mode 100644 index 1bbfac1..0000000 --- a/FAQ.md +++ /dev/null @@ -1,227 +0,0 @@ -# FAQ — Часто задаваемые вопросы - -## Установка и запуск - -### Где получить API credentials? - -1. Перейдите на https://my.telegram.org/apps -2. Войдите с вашим номером телефона -3. Создайте новое приложение -4. Скопируйте `api_id` и `api_hash` - -### Где хранить credentials? - -**Рекомендуется** (приоритет 1): -```bash -~/.config/tele-tui/credentials -``` - -**Альтернатива** (приоритет 2): -```bash -.env # в корне проекта -``` - -### Ошибка "Telegram API credentials not found!" - -Убедитесь, что вы создали файл credentials (см. выше) с правильным форматом: -``` -API_ID=12345678 -API_HASH=abcdef1234567890abcdef1234567890 -``` - -### Где хранится сессия Telegram? - -В папке `./tdlib_data/` в директории запуска приложения. Эта папка содержит: -- Токены авторизации -- Кеш сообщений -- Другие данные TDLib - -**Важно**: Не удаляйте эту папку, иначе придётся заново авторизоваться. - -## Использование - -### Как переключаться между папками? - -Нажмите клавиши `1-9` для переключения между первыми 9 папками Telegram. - -### Как искать сообщения в чате? - -1. Откройте чат -2. Нажмите `Ctrl+F` -3. Введите поисковый запрос -4. Используйте `n` / `N` для навигации по результатам - -### Как скопировать текст сообщения? - -1. При пустом поле ввода нажмите `↑` для выбора сообщения -2. Нажмите `y` (или `н` на русской раскладке) -3. Текст скопирован в системный буфер обмена - -### Как ответить на сообщение? - -1. Выберите сообщение (`↑` при пустом инпуте) -2. Нажмите `r` (или `к` на русской раскладке) -3. Введите ответ -4. Нажмите `Enter` - -### Как удалить сообщение? - -1. Выберите сообщение -2. Нажмите `d` / `в` / `Delete` -3. Подтвердите удаление: `y` / `Enter` - -### Как добавить реакцию? - -1. Выберите сообщение -2. Нажмите `e` (или `у` на русской раскладке) -3. Выберите emoji стрелками -4. Нажмите `Enter` - -### Почему не работают хоткеи на русской раскладке? - -Убедитесь, что вы используете **русскую раскладку**, а не транслит. Поддерживаемые комбинации: -- `р о л д` → `h j k l` (навигация) -- `к` → `r` (reply) -- `а` → `f` (forward) -- `в` → `d` (delete) -- `н` → `y` (copy) -- `у` → `e` (react) - -## Конфигурация - -### Где находится конфигурационный файл? - -```bash -~/.config/tele-tui/config.toml -``` - -Создаётся автоматически при первом запуске. - -### Как изменить часовой пояс? - -Отредактируйте `~/.config/tele-tui/config.toml`: - -```toml -[general] -timezone = "+05:00" # Ваш часовой пояс -``` - -### Как изменить цветовую схему? - -Отредактируйте секцию `[colors]` в конфиге: - -```toml -[colors] -incoming_message = "cyan" -outgoing_message = "lightgreen" -selected_message = "lightyellow" -``` - -Поддерживаемые цвета: black, red, green, yellow, blue, magenta, cyan, gray, white, darkgray, lightred, lightgreen, lightyellow, lightblue, lightmagenta, lightcyan. - -### Нужно ли перезапускать приложение после изменения конфига? - -Да, изменения в `config.toml` применяются только при запуске приложения. - -## Проблемы - -### Приложение зависает при запуске - -Возможные причины: -1. **Нет интернета**: проверьте подключение -2. **TDLib не может подключиться**: проверьте firewall/прокси -3. **Неверные credentials**: проверьте API_ID и API_HASH - -### Сообщения не загружаются - -1. Проверьте статус сети в футере (внизу экрана) -2. Попробуйте обновить: `Ctrl+R` -3. Перезапустите приложение - -### "Deleted Account" в списке чатов - -Это пользователи, которые удалили свой аккаунт Telegram. Они автоматически фильтруются и не отображаются в списке. - -### Не отображаются медиафайлы - -Медиафайлы (фото, видео, голосовые, стикеры) отображаются как заглушки: [Фото], [Видео], [Голосовое], [Стикер]. Полная поддержка медиа может быть добавлена в будущем. - -### Ошибка компиляции при сборке - -**TDLib download failed**: -- Проверьте интернет-соединение -- Убедитесь, что у вас достаточно места на диске - -**Linking with cc failed**: -- macOS: `xcode-select --install` -- Linux: `sudo apt-get install build-essential` -- Windows: установите Visual Studio Build Tools - -### Как сбросить сессию? - -Удалите папку `tdlib_data/`: -```bash -rm -rf tdlib_data/ -``` - -При следующем запуске потребуется заново авторизоваться. - -## Производительность - -### Приложение тормозит - -Проверьте: -1. Количество открытых чатов (лимит 200) -2. Количество сообщений в открытом чате (лимит 500) -3. Размер терминала (минимум 80x20) - -Приложение автоматически очищает старые данные при достижении лимитов. - -### Высокое использование памяти - -Это нормально при большом количестве чатов и сообщений. Приложение использует LRU кеши с ограничениями: -- 500 пользователей в кеше -- 500 сообщений на чат -- 200 чатов - -## Разработка - -### Как внести вклад в проект? - -См. [CONTRIBUTING.md](CONTRIBUTING.md) - -### Где найти план развития? - -См. [ROADMAP.md](ROADMAP.md) - -### Как сообщить о баге? - -Создайте issue на GitHub с описанием: -- Шаги для воспроизведения -- Ожидаемое и фактическое поведение -- Версия ОС и Rust -- Логи (если есть) - -## Безопасность - -### Безопасно ли хранить credentials в файле? - -Да, если вы: -1. Используете `~/.config/tele-tui/credentials` -2. Установили права доступа: `chmod 600 ~/.config/tele-tui/credentials` -3. Не коммитите этот файл в git (уже в `.gitignore`) - -### Что делать при компрометации credentials? - -1. Удалите приложение на https://my.telegram.org/apps -2. Создайте новое приложение с новыми credentials -3. Обновите файл `credentials` -4. Удалите папку `tdlib_data/` и авторизуйтесь заново - -### Включена ли двухфакторная аутентификация? - -Если вы включили 2FA в Telegram, приложение запросит пароль при первой авторизации. - -## Ещё вопросы? - -Создайте issue на GitHub или свяжитесь с maintainers. diff --git a/INSTALL.md b/INSTALL.md deleted file mode 100644 index 776834a..0000000 --- a/INSTALL.md +++ /dev/null @@ -1,122 +0,0 @@ -# Установка tele-tui - -## Требования - -- **Rust**: версия 1.70 или выше ([установить](https://rustup.rs/)) -- **TDLib**: скачивается автоматически через tdlib-rs - -## Шаг 1: Клонирование репозитория - -```bash -git clone https://github.com/your-username/tele-tui.git -cd tele-tui -``` - -## Шаг 2: Получение API credentials - -1. Перейдите на https://my.telegram.org/apps -2. Войдите с вашим номером телефона -3. Создайте новое приложение -4. Скопируйте **api_id** и **api_hash** - -## Шаг 3: Настройка credentials - -### Вариант A: XDG config directory (рекомендуется) - -Создайте файл `~/.config/tele-tui/credentials`: - -```bash -mkdir -p ~/.config/tele-tui -cat > ~/.config/tele-tui/credentials << EOF -API_ID=your_api_id_here -API_HASH=your_api_hash_here -EOF -``` - -### Вариант B: .env файл - -Создайте файл `.env` в корне проекта: - -```bash -cp credentials.example .env -# Отредактируйте .env и вставьте ваши credentials -``` - -## Шаг 4: Сборка - -```bash -cargo build --release -``` - -## Шаг 5: Запуск - -```bash -cargo run --release -``` - -Или запустите скомпилированный бинарник: - -```bash -./target/release/tele-tui -``` - -## Первый запуск - -При первом запуске вам нужно будет: - -1. Ввести номер телефона (с кодом страны, например: +79991234567) -2. Ввести код подтверждения из Telegram -3. Если включена 2FA — ввести пароль - -Сессия сохраняется в `./tdlib_data/`, при следующем запуске авторизация не потребуется. - -## Настройка (опционально) - -Конфигурационный файл создаётся автоматически при первом запуске в `~/.config/tele-tui/config.toml`. - -Вы можете отредактировать его для настройки: -- Часового пояса -- Цветовой схемы - -Пример конфигурации см. в файле `config.toml.example`. - -## Устранение неполадок - -### "Telegram API credentials not found!" - -Убедитесь, что вы создали файл credentials (см. Шаг 3). - -### "error: linking with `cc` failed" - -Убедитесь, что у вас установлен C компилятор: -- macOS: `xcode-select --install` -- Linux: `sudo apt-get install build-essential` (Debian/Ubuntu) -- Windows: установите Visual Studio Build Tools - -### TDLib download failed - -Проверьте подключение к интернету. TDLib скачивается автоматически при первой сборке. - -## Обновление - -```bash -git pull -cargo build --release -``` - -Ваши credentials и конфигурация сохранятся. - -## Удаление - -Чтобы полностью удалить приложение и все данные: - -```bash -# Удалить проект -rm -rf tele-tui/ - -# Удалить конфигурацию и credentials -rm -rf ~/.config/tele-tui/ - -# Удалить сессию Telegram (опционально, потребуется новая авторизация) -# rm -rf ./tdlib_data/ -``` diff --git a/REFACTORING_OPPORTUNITIES.md b/REFACTORING_OPPORTUNITIES.md deleted file mode 100644 index 8a3e671..0000000 --- a/REFACTORING_OPPORTUNITIES.md +++ /dev/null @@ -1,855 +0,0 @@ -# Возможности для рефакторинга - -> Результаты аудита кодовой базы от 2026-02-02 -> Обновлено: 2026-02-04 -> Статус: В работе (2/10 категорий полностью завершены, 3 частично) - -## Оглавление - -1. [Дублирование кода](#1-дублирование-кода) -2. [Большие файлы/функции](#2-большие-файлыфункции) -3. [Сложная вложенность](#3-сложная-вложенность) -4. [Нарушение Single Responsibility](#4-нарушение-single-responsibility) -5. [Плохая инкапсуляция](#5-плохая-инкапсуляция) -6. [Отсутствующие абстракции](#6-отсутствующие-абстракции) -7. [Несогласованность](#7-несогласованность) -8. [Перекрытие функциональности](#8-перекрытие-функциональности) -9. [Проблемы производительности](#9-проблемы-производительности) -10. [Отсутствующие архитектурные паттерны](#10-отсутствующие-архитектурные-паттерны) - ---- - -## 1. Дублирование кода - -**Приоритет:** 🔴 Высокий -**Статус:** ✅ ПОЛНОСТЬЮ ЗАВЕРШЕНО! (2026-02-02) -**Объем:** 15-20% кодовой базы → Устранено! - -### Проблемы - -- **Timeout/Retry паттерны** (~20 экземпляров в обработке ввода) - - Повторяющаяся логика таймаутов в `src/input/main_input.rs` - - Одинаковые паттерны retry в разных обработчиках - -- **Обработка модальных окон** (5+ мест) - - Логика открытия/закрытия модалок дублируется - - Валидация ввода в модальных окнах повторяется - - Обработка Escape для закрытия модалок в каждом месте - -- **Паттерны валидации** - - Проверка пустых строк - - Валидация ID чатов/сообщений - - Проверка длины текста - -### Решение - -- [x] Создать `retry_utils.rs` с функциями `with_timeout()`, `with_retry()` - **Выполнено и интегрировано** (2026-02-02) - - Создан `src/utils/retry.rs` с тремя функциями: `with_timeout()`, `with_timeout_msg()`, `with_timeout_ignore()` - - Заменены ВСЕ прямые использования `tokio::time::timeout` (8+ мест: main_input.rs, auth.rs, main.rs) - - Код стал чище и короче (убрано вложенное Ok/Err матчинг) - - **100% покрытие** - больше нет прямых timeout вызовов -- [x] Создать `modal_handler.rs` с общей логикой модальных окон - **Выполнено** (2026-02-01) - - Создан `src/utils/modal_handler.rs` (120+ строк) - - 4 функции: `handle_modal_key()`, `should_close_modal()`, `should_confirm_modal()`, `handle_yes_no()` - - Enum `ModalAction` для type-safe обработки - - Поддержка английской и русской раскладки (y/д, n/т) - - 4 unit теста (все проходят) -- [x] Создать `validation.rs` с переиспользуемыми валидаторами - **Выполнено и интегрировано** (2026-02-02) - - Создан `src/utils/validation.rs` (180+ строк) - - 7 функций валидации: `is_non_empty()`, `is_within_length()`, `is_valid_chat_id()`, `is_valid_message_id()`, `is_valid_user_id()`, `has_items()`, `validate_text_input()` - - Покрывает все основные паттерны валидации - - 7 unit тестов (все проходят) - - **Интегрировано в 4 местах:** auth.rs (phone/code/password), main_input.rs (message validation) - -### Файлы - -- `src/input/main_input.rs` -- `src/app/handlers/*.rs` -- `src/ui/modals/*.rs` - ---- - -## 2. Большие файлы/функции - -**Приоритет:** 🔴 Высокий -**Статус:** ✅ **ПОЛНОСТЬЮ ЗАВЕРШЕНО!** (обновлено 2026-02-04) -**Объем:** Все 4 файла отрефакторены! (4/4, 100%! 🎉) - -### Проблемы - -| Файл | Строки | Проблема | Статус | -|------|--------|----------|--------| -| `src/input/main_input.rs` | ~~1164~~ → ~1200 | ~~Одна функция `handle()` на ~800 строк~~ | ✅ **РЕШЕНО** (handle() → 82 строки) | -| `src/tdlib/client.rs` | ~~1259~~ → 599 | ~~Смешение facade и бизнес-логики~~ | ✅ **РЕШЕНО** (1259 → 599 строк, -52%) | -| `src/ui/messages.rs` | 905 | ~~Рендеринг всех типов сообщений~~ | ✅ **НЕ ТРЕБУЕТСЯ** (render() → 92 строки, Phase 5) | -| `src/tdlib/messages.rs` | ~~850~~ → 757 | ~~Обработка всех типов обновлений сообщений~~ | ✅ **РЕШЕНО** (convert_message() → 57 строк, -62%) | - -### Решение - -#### 2.1. Разделить `src/input/main_input.rs` - ✅ **ЗАВЕРШЕНО** (2026-02-03) - -**Phase 1-2** (2026-02-02): -- [x] Создана структура `src/input/handlers/` (7 модулей) - ПОДГОТОВКА -- [x] Создан `handlers/clipboard.rs` (~100 строк) - извлечён из main_input -- [x] Создан `handlers/global.rs` (~90 строк) - извлечён из main_input -- [x] Созданы заглушки: `profile.rs`, `search.rs`, `modal.rs`, `messages.rs`, `chat_list.rs` - -**Phase 2-3** (2026-02-03): -- [x] **Извлечено 13 специализированных функций-обработчиков** (~946 строк): - - `handle_open_chat_keyboard_input()` (~129 строк) - - `handle_chat_list_navigation()` (~34 строки) - - `handle_profile_mode()` (~120 строк) - - `handle_message_search_mode()` (~73 строки) - - `handle_pinned_mode()` (~42 строки) - - `handle_reaction_picker_mode()` (~90 строк) - - `handle_delete_confirmation()` (~60 строк) - - `handle_forward_mode()` (~52 строки) - - `handle_chat_search_mode()` (~43 строки) - - `handle_enter_key()` (~145 строк) - - `handle_escape_key()` (~35 строк) - - `handle_message_selection()` (~95 строк) - - `handle_profile_open()` (~28 строк) - -**Phase 4** (2026-02-03): -- [x] **Упрощена вложенность** (early returns, let-else guards) -- [x] **Извлечено 3 вспомогательных функции**: - - `edit_message()` (~50 строк) - - `send_new_message()` (~55 строк) - - `perform_message_search()` (~20 строк) - -**Итоговый результат**: -- ✅ Функция `handle()` сократилась с **891 до 82 строк** (91% сокращение! 🎉) -- ✅ Глубина вложенности: **6+ уровней → 2-3 уровня** -- ✅ Все 196 тестов проходят успешно -- ✅ Код стал **линейным и простым для понимания** - -**Примечание**: Вместо создания отдельных файлов в handlers/ (что привело бы к поломке), мы выбрали подход извлечения функций внутри main_input.rs. Это позволило радикально упростить код без риска регрессий. - -#### 2.2. Разделить `src/tdlib/client.rs` - ✅ **ЗАВЕРШЕНО** (2026-02-04) - -**Этап 1** (2026-02-04): Извлечение Update Handlers -- [x] Создан модуль `src/tdlib/update_handlers.rs` (302 строки) -- [x] **Извлечено 8 handler функций** (~350 строк): - - `handle_new_message_update()` — добавление новых сообщений (44 строки) - - `handle_chat_action_update()` — статус набора текста (32 строки) - - `handle_chat_position_update()` — управление позициями чатов (36 строк) - - `handle_user_update()` — обработка информации о пользователях (40 строк) - - `handle_message_interaction_info_update()` — обновление реакций (44 строки) - - `handle_message_send_succeeded_update()` — успешная отправка (35 строк) - - `handle_chat_draft_message_update()` — черновики сообщений (15 строк) - - `handle_auth_state()` — изменение состояния авторизации (10 строк) -- [x] Обновлён `handle_update()` для делегирования в update_handlers -- [x] Результат: **client.rs 1259 → 983 строки** (22% сокращение) - -**Этап 2** (2026-02-04): Извлечение Message Converter -- [x] Создан модуль `src/tdlib/message_converter.rs` (250 строк) -- [x] **Извлечено 6 conversion функций** (~240 строк): - - `convert_message()` — основная конвертация TDLib → MessageInfo (150+ строк) - - `extract_reply_info()` — извлечение reply информации (30 строк) - - `extract_forward_info()` — извлечение forward информации (25 строк) - - `extract_reactions()` — извлечение реакций (20 строк) - - `get_origin_sender_name()` — получение имени отправителя (15 строк) - - `update_reply_info_from_loaded_messages()` — обновление reply из кэша (30 строк) -- [x] Исправлены ошибки компиляции с неверными именами полей -- [x] Обновлены вызовы в update_handlers.rs -- [x] Результат: **client.rs 983 → 754 строки** (23% сокращение) - -**Этап 3** (2026-02-04): Извлечение Chat Helpers -- [x] Создан модуль `src/tdlib/chat_helpers.rs` (149 строк) -- [x] **Извлечено 3 helper функции** (~140 строк): - - `find_chat_mut()` — поиск чата по ID (15 строк) - - `update_chat()` — обновление чата через closure (15 строк, используется 9+ раз) - - `add_or_update_chat()` — добавление/обновление чата в списке (110+ строк) -- [x] Использован sed для замены вызовов методов по всей кодовой базе -- [x] Результат: **client.rs 754 → 599 строк** (21% сокращение) - -**Итоговый результат**: -- ✅ Файл `client.rs` сократился с **1259 до 599 строк** (52% сокращение! 🎉) -- ✅ Создано **3 новых модуля** с чёткой ответственностью: - - `update_handlers.rs` — обработка всех типов TDLib Update - - `message_converter.rs` — конвертация TDLib Message → MessageInfo - - `chat_helpers.rs` — утилиты для работы с чатами -- ✅ Все **590+ тестов** проходят успешно -- ✅ Код стал **модульным и лучше организованным** -- ✅ `TdClient` теперь ближе к **facade pattern** (делегирует в специализированные модули) - -#### 2.3. Упростить `src/ui/messages.rs` - ✅ **ЗАВЕРШЕНО** (Phase 5, 2026-02-03) - -**Уже выполнено в Phase 5**: -- [x] Извлечены 4 функции рендеринга (~350 строк): - - `render_chat_header()` — заголовок с typing status (~47 строк) - - `render_pinned_bar()` — панель закреплённого сообщения (~30 строк) - - `render_message_list()` — список сообщений с автоскроллом (~98 строк) - - `render_input_box()` — input с режимами (forward/select/edit/reply) (~146 строк) -- [x] Функция `render()` сократилась с **390 до 92 строк** (76% сокращение! 🎉) -- [x] Глубина вложенности: **6+ уровней → 2-3 уровня** -- [x] Код стал **модульным и простым для понимания** - -**Итоговый результат**: -- ✅ Файл остался цельным (905 строк), но хорошо организован -- ✅ Главная функция `render()` компактная (92 строки) -- ✅ Все вспомогательные функции извлечены (render_search_mode, render_pinned_mode, и др.) -- ✅ **Дальнейшее разделение не требуется** — цели достигнуты - -#### 2.4. Упростить `src/tdlib/messages.rs` - ✅ **ЗАВЕРШЕНО** (2026-02-04) - -**Этап 1** (2026-02-04): Извлечение Message Conversion Helpers -- [x] Создан модуль `src/tdlib/message_conversion.rs` (158 строк) -- [x] **Извлечено 6 вспомогательных функций**: - - `extract_content_text()` — извлечение текста из различных типов сообщений (~80 строк) - - `extract_entities()` — извлечение форматирования (~10 строк) - - `extract_sender_name()` — получение имени отправителя с API вызовом (~15 строк) - - `extract_forward_info()` — информация о пересылке (~12 строк) - - `extract_reply_info()` — информация об ответе (~15 строк) - - `extract_reactions()` — реакции на сообщение (~26 строк) -- [x] Метод `convert_message()` сократился с **150 до 57 строк** (62% сокращение! 🎉) -- [x] Результат: **messages.rs 850 → 757 строк** (11% сокращение) - -**Итоговый результат**: -- ✅ Файл `messages.rs` сократился до **757 строк** -- ✅ Создан модуль **message_conversion.rs** с переиспользуемыми функциями -- ✅ Метод `convert_message()` теперь **компактный и читаемый** (57 строк) -- ✅ Все **629 тестов** проходят успешно -- ✅ **Дальнейшее разделение не требуется** — MessageManager хорошо организован - -### Файлы - -- `src/input/main_input.rs` -- `src/tdlib/client.rs` -- `src/ui/messages.rs` -- `src/tdlib/messages.rs` - ---- - -## 3. Сложная вложенность - -**Приоритет:** 🟡 Средний -**Статус:** ✅ **ПОЛНОСТЬЮ ЗАВЕРШЕНО!** (обновлено 2026-02-04) -**Объем:** ~30 функций → 0 функций (все проблемные решены) - -### Проблемы - -- ~~4-5 уровней вложенности в обработке ввода~~ ✅ **Решено в main_input.rs** -- Глубокая вложенность в обработке обновлений TDLib -- ~~Множественные `if let` / `match` вложенные друг в друга~~ ✅ **Решено в main_input.rs** - -### Примеры - -```rust -// src/input/main_input.rs - было (типичный пример) -if let Some(chat_id) = app.selected_chat { - if let Some(message_id) = app.selected_message { - if app.is_message_outgoing(chat_id, message_id) { - match key.code { - // еще больше вложенности (6+ уровней) - } - } - } -} - -// Стало (после Phase 4 рефакторинга) -let Some(chat_id) = app.selected_chat else { return Ok(false) }; -let Some(message_id) = app.selected_message else { return Ok(false) }; - -if !app.is_message_outgoing(chat_id, message_id) { - return Ok(false); // early return -} -// Линейная логика (2-3 уровня максимум) -``` - -### Решение - -#### Выполнено в `src/input/main_input.rs` (2026-02-03) - -- [x] **Применены early returns** - уменьшили вложенность с 6+ до 2-3 уровней -- [x] **Извлечена вложенная логика** в 3 функции: - - `edit_message()` — редактирование сообщения (~50 строк) - - `send_new_message()` — отправка нового сообщения (~55 строк) - - `perform_message_search()` — поиск по сообщениям (~20 строк) -- [x] **Использованы let-else guard clauses** — современный Rust паттерн -- [x] **Упрощены 6 функций**: - - `handle_profile_mode()` — упрощён блок Enter с let-else - - `handle_profile_open()` — применён early return guard - - `handle_enter_key()` — разделена на части, сокращена с ~130 до ~40 строк - - `handle_message_search_mode()` — извлечена логика поиска - - `handle_escape_key()` — преобразован в early returns - - `handle_message_selection()` — применены let-else guards - -**Результат Phase 4**: -- ✅ Глубина вложенности: **6+ уровней → 2-3 уровня** -- ✅ Код стал **максимально линейным и читаемым** -- ✅ Применены современные Rust паттерны (let-else, guards) - -#### Выполнено в `src/tdlib/client.rs` (2026-02-03, Этап 3) - -- [x] **Добавлены helper методы** для устранения дублирования: - - `find_chat_mut()` — поиск чата по ID - - `update_chat()` — обновление чата через closure (использовано 9+ раз) -- [x] **Извлечено 5 handler методов** из `handle_update()`: - - `handle_chat_position_update()` — управление позициями чатов (43 строки) - - `handle_user_update()` — обработка информации о пользователях (46 строк) - - `handle_message_interaction_info_update()` — обновление реакций (44 строки) - - `handle_message_send_succeeded_update()` — успешная отправка (38 строк) - - `handle_chat_draft_message_update()` — черновики (18 строк) -- [x] **Упрощено 7 функций** с применением let-else guards, early returns, unwrap_or_else: - - `handle_chat_action_update()` — статус набора текста (4 → 2 уровня) - - `handle_new_message_update()` — добавление сообщений (3 → 2 уровня) - - `handle_chat_draft_message_update()` — черновики (if-let → match) - - `handle_user_update()` — usernames (вложенные if-let → and_then) - - `convert_message()` — кэш имён (if-let → unwrap_or_else) - - `extract_reply_info()` — reply информация (вложенные if-let → map/or_else) - - `update_reply_info_from_loaded_messages()` — обновление reply (4 → 1-2 уровня) - -**Результат Этапа 3 (client.rs)**: -- ✅ Функция `handle_update()` сократилась с **268 до 122 строк** (54% сокращение!) -- ✅ Устранено дублирование: ~9 повторений pattern → 2 helper метода -- ✅ Глубина вложенности: **4-5 уровней → 2-3 уровня** -- ✅ Применены modern patterns: let-else guards, early returns, filter chains - -#### Дополнительные улучшения вложенности (2026-02-04) - -- [x] **Упрощена `src/tdlib/messages.rs`** (строки 718-755) - - `fetch_missing_reply_info()`: 7 уровней → 2-3 уровня - - Извлечена функция `fetch_and_update_reply()` - - Использованы let-else guards и iterator chains - - Максимальная вложенность: **44 → 28 пробелов** - -- [x] **Упрощена `src/tdlib/messages.rs`** (строки 147-182) - - `get_chat_history()` retry loop: 6 уровней → 3 уровня - - Извлечен `messages_obj` после match - - Early continue для пустых результатов - - Использован `.flatten()` вместо вложенного if-let - -- [x] **Упрощена `src/input/main_input.rs`** (строки 500-546) - - `handle_forward_mode()`: 7 уровней → 2-3 уровня - - Извлечена функция `forward_selected_message()` - - Использованы early returns (let-else guards) - - Максимальная вложенность: **40 → 36 пробелов** - -- [x] **Упрощена `src/input/main_input.rs`** (reaction picker) - - Извлечена функция `send_reaction()` - - Использованы let-else guards - - Вложенность: 5 уровней → 2-3 уровня - -- [x] **Упрощена `src/input/main_input.rs`** (scroll + load older) - - Извлечена функция `load_older_messages_if_needed()` - - Использованы early returns - - Вложенность: 6 уровней → 2-3 уровня - -- [x] **Упрощена `src/config.rs`** (строки 563-609) - - `load_credentials()`: 7 уровней → 2-3 уровня - - Извлечены функции `load_credentials_from_file()` и `load_credentials_from_env()` - - Использованы `?` operator для Option chains - - Максимальная вложенность: **36 → 32 пробелов** - -**Итоговый результат**: -- ✅ Все файлы с вложенностью >32 пробелов обработаны -- ✅ Применены современные Rust паттерны (let-else guards, early returns, ? operator, iterator chains) -- ✅ Извлечено 8 новых функций для разделения ответственности -- ✅ Максимальная вложенность во всем проекте: **≤32 пробелов (8 уровней)** - -### Файлы - -- ✅ `src/input/main_input.rs` — **ПОЛНОСТЬЮ ЗАВЕРШЕНО** (Phase 4 + доп. улучшения: 40 → 36 пробелов) -- ✅ `src/tdlib/client.rs` — **ЗАВЕРШЕНО** (Этап 3: 268 → 122 строки в handle_update) -- ✅ `src/tdlib/messages.rs` — **ПОЛНОСТЬЮ ЗАВЕРШЕНО** (44 → 28 пробелов) -- ✅ `src/config.rs` — **ПОЛНОСТЬЮ ЗАВЕРШЕНО** (36 → 32 пробелов) -- ✅ Все остальные модули — **проверены, вложенность приемлема** (≤32 пробелов) - ---- - -## 4. Нарушение Single Responsibility - -**Приоритет:** 🟡 Средний -**Статус:** ❌ Не начато -**Объем:** 2 основных структуры - -### Проблемы - -#### 4.1. `App` struct (50+ методов) - -Смешивает ответственности: -- UI state management -- Business logic -- TDLib interaction -- Input handling -- Search logic -- Profile management -- Folder management - -#### 4.2. `TdClient` (facade + бизнес-логика) - -Смешивает: -- Facade pattern (делегирование) -- Update processing -- Cache management -- Network operations - -### Решение - -#### Разделить `App` - -- [ ] Создать `ChatListState` (состояние списка чатов) -- [ ] Создать `MessageViewState` (состояние просмотра сообщений) -- [ ] Создать `ComposeState` (состояние написания сообщения) -- [ ] Создать `SearchState` (состояние поиска) -- [ ] Создать `ProfileState` (состояние профиля) -- [ ] `App` становится координатором этих state объектов - -#### Разделить `TdClient` - -- [ ] `TdClient` только facade (делегирование) -- [ ] Бизнес-логика в `MessageService`, `ChatService`, etc. -- [ ] Update processing в отдельном модуле - -### Файлы - -- `src/app/mod.rs` -- `src/tdlib/client.rs` - ---- - -## 5. Плохая инкапсуляция - -**Приоритет:** 🔴 Высокий -**Статус:** ✅ Частично выполнено (2026-02-01) -**Объем:** Вся структура `App` - -### Проблемы - -- **22 публичных поля** в `App` - ```rust - pub struct App { - pub td_client: TdClient, - pub chats: Vec, - pub selected_chat: Option, - pub messages: HashMap>, - // ... еще 18 полей - } - ``` - -- **Прямой доступ везде** - ```rust - app.selected_chat = Some(chat_id); // Плохо - app.chats.push(new_chat); // Плохо - app.messages.clear(); // Плохо - ``` - -- **Тесты манипулируют внутренностями** - ```rust - app.td_client.user_cache.chat_user_ids.insert(...); // Слишком глубоко - ``` - -### Решение - -- [x] Сделать критичные поля приватными - **Частично выполнено** (2026-02-01) - - ✅ `config` сделан приватным (readonly через getter `app.config()`) - - ✅ Добавлены 30+ методов-геттеров и сеттеров для всех полей - - ⏳ Остальные поля оставлены pub для совместимости (требуется массовый рефакторинг) -- [x] Добавить getter методы где нужно - **Выполнено** - - 30+ методов: `phone_input()`, `set_phone_input()`, `screen()`, `set_screen()`, `is_loading()`, и т.д. -- [ ] Полная инкапсуляция всех полей (требует обновления 170+ мест в коде) -- [ ] Создать методы для операций (вместо прямого доступа) - ```rust - // Вместо app.selected_chat = Some(chat_id) - app.select_chat(chat_id); // Уже есть! - - // Вместо app.chats.push(new_chat) - app.add_chat(new_chat); // TODO - ``` - -### Файлы - -- `src/app/mod.rs` -- `src/app/state.rs` (новый) -- Все тесты - ---- - -## 6. Отсутствующие абстракции - -**Приоритет:** 🟡 Средний -**Статус:** ✅ Частично выполнено (2026-02-04) -**Объем:** 3 основные абстракции (2/3 завершены, 1/3 уже была) - -### Проблемы - -#### 6.1. Создать `KeyHandler` trait ✅ ЗАВЕРШЕНО! (2026-02-04) - -- [x] Создать `src/input/key_handler.rs` - **Выполнено** (380+ строк) - - Enum `KeyResult` (Handled, HandledNeedsRedraw, NotHandled, Quit) - - Trait `KeyHandler` с методом `handle_key()` и `priority()` - - Struct `GlobalKeyHandler` - обработчик глобальных команд (Quit, OpenSearch) - - Struct `ChatListKeyHandler` - навигация по списку чатов, выбор папок - - Struct `MessageViewKeyHandler` - скролл сообщений, поиск в чате - - Struct `MessageSelectionKeyHandler` - действия с выбранным сообщением - - Struct `KeyHandlerChain` - цепочка обработчиков с приоритетами - - 3 unit теста (все проходят) -- [ ] Интегрировать в main_input.rs (заменить текущую логику) -- [ ] Добавить недостающие методы в App (enter_search_mode и т.д.) - -#### 6.2. Нет абстракции для network operations - -Timeout/retry логика дублируется: - -```rust -// Повторяется ~20 раз -let result = tokio::time::timeout( - Duration::from_millis(100), - operation() -).await; -``` - -#### 6.3. Хардкод горячих клавиш - -Невозможно изменить без правки кода: - -```rust -KeyCode::Char('e') => edit_message(), // Хардкод -KeyCode::Char('d') => delete_message(), // Хардкод -``` - -### Решение - -#### 6.1. Создать `KeyHandler` trait - -- [ ] Создать `src/input/key_handler.rs` - ```rust - trait KeyHandler { - fn handle_key(&mut self, app: &mut App, key: KeyEvent) -> Result; - } - ``` -- [ ] Реализовать для каждого экрана: - - `ChatListKeyHandler` - - `MessagesKeyHandler` - - `ComposeKeyHandler` - - `SearchKeyHandler` - -#### 6.2. Создать network utilities - -- [ ] Создать `src/utils/network.rs` - ```rust - async fn with_timeout(f: F, timeout_ms: u64) -> Result - async fn with_retry(f: F, max_retries: u32) -> Result - ``` - -#### 6.3. Создать систему горячих клавиш ✅ ЗАВЕРШЕНО! (2026-02-04) - -- [x] Создать `src/config/keybindings.rs` - **Выполнено** - - Enum `Command` с 40+ командами (навигация, чат, сообщения, input) - - Struct `KeyBinding` с поддержкой модификаторов (Ctrl, Shift, Alt и т.д.) - - Struct `Keybindings` с HashMap> - - Поддержка множественных bindings для одной команды (EN/RU раскладки) - - Сериализация/десериализация KeyCode и KeyModifiers - - 4 unit теста (все проходят) -- [ ] Интегрировать в приложение (вместо HotkeysConfig) -- [ ] Загружать из конфига (опционально, с fallback на defaults) - -### Файлы - -- `src/input/key_handler.rs` (новый) -- `src/utils/network.rs` (новый) -- `src/config/keybindings.rs` (новый) - ---- - -## 7. Несогласованность - -**Приоритет:** 🟢 Низкий -**Статус:** ❌ Не начато -**Объем:** Вся кодовая база - -### Проблемы - -#### 7.1. Разные типы ошибок - -```rust -// В одних местах -Result - -// В других -Result> - -// В третьих -Result // с неявным типом ошибки -``` - -#### 7.2. Разные паттерны state management - -- В одних местах флаги (`is_editing: bool`) -- В других энумы (`EditMode::Active`) -- В третьих Option (`editing_message: Option`) - -#### 7.3. Разные подходы к валидации - -- Иногда в UI слое -- Иногда в бизнес-логике -- Иногда в обработчиках ввода - -### Решение - -- [ ] Стандартизировать обработку ошибок (один тип ошибки) -- [ ] Выбрать единый подход к state management (enum-based) -- [ ] Определить слой для валидации (бизнес-логика) -- [ ] Создать style guide в документации - -### Файлы - -- Вся кодовая база - ---- - -## 8. Перекрытие функциональности - -**Приоритет:** 🟡 Средний -**Статус:** ✅ Выполнено (2026-02-04) -**Объем:** 2 основные области (2/2 завершены) - -### Проблемы - -#### 8.1. Централизовать фильтрацию чатов ✅ ЗАВЕРШЕНО! (2026-02-04) - -- [x] Создать `src/app/chat_filter.rs` - **Выполнено** (470+ строк) - - Struct `ChatFilterCriteria` с builder pattern - - Поддержка фильтрации по: папке, поиску, pinned, unread, mentions, muted, archived - - Struct `ChatFilter` с методами фильтрации - - Enum `ChatSortOrder` для сортировки (ByLastMessage, ByTitle, ByUnreadCount, PinnedFirst) - - Методы подсчёта: count, count_unread, count_unread_mentions - - 6 unit тестов (все проходят) -- [ ] Заменить дублирующуюся логику в App и UI на ChatFilter -- [ ] Удалить старые методы фильтрации из App - -#### 8.2. Централизовать обработку сообщений ✅ ЗАВЕРШЕНО! (2026-02-04) - -- [x] Создать `src/app/message_service.rs` - **Выполнено** (508+ строк) - - Struct `MessageGroup` для группировки по дате - - Struct `SenderGroup` для группировки по отправителю - - Struct `MessageSearchResult` с контекстом поиска - - Struct `MessageService` с 13 методами бизнес-логики: - - `group_by_date()` - группировка сообщений по датам - - `group_by_sender()` - группировка по отправителю - - `search()` - полнотекстовый поиск с контекстом - - `find_next()` / `find_previous()` - навигация по результатам - - `filter_by_sender()` / `filter_unread()` - фильтрация - - `find_by_id()` / `find_index_by_id()` - поиск по ID - - `get_last_n()` - получение последних N сообщений - - `get_in_date_range()` - фильтрация по диапазону дат - - `count_by_sender_type()` - статистика по типам - - `create_index()` - создание быстрого индекса - - 7 unit тестов (все проходят) -- [ ] Заменить разрозненную логику в App/UI на MessageService -- [ ] Чёткое разделение: TDLib → Service → UI - -### Решение - -#### 8.1. Централизовать фильтрацию ✅ - -- [x] Создать `src/app/chat_filter.rs` - **Выполнено** -- [x] Один источник правды для фильтрации - **Выполнено** -- [ ] UI и App используют его - TODO (требует интеграции) - -#### 8.2. Четко разделить слои обработки сообщений ✅ - -- [x] `tdlib/messages.rs` - только получение и преобразование - **Выполнено** -- [x] `app/message_service.rs` - бизнес-логика - **Выполнено** -- [x] `ui/messages.rs` - только рендеринг - **Было уже реализовано** - -### Файлы - -- `src/app/chat_filter.rs` (новый) -- `src/app/message_service.rs` (новый) -- `src/tdlib/messages.rs` -- `src/ui/messages.rs` - ---- - -## 9. Проблемы производительности - -**Приоритет:** 🟢 Низкий -**Статус:** ❌ Не начато -**Объем:** Локальные оптимизации - -### Проблемы - -#### 9.1. Множественные клоны в обработке ввода - -```rust -let text = app.input_text.clone(); // Клон -let chat_id = app.selected_chat.clone(); // Клон -// Используются только для чтения -``` - -#### 9.2. Нет кеширования результатов поиска - -- Каждый поиск выполняется заново -- Нет инвалидации кеша при изменениях - -#### 9.3. Неэффективная LRU cache - -- `Vec::retain()` + `Vec::push()` на каждый доступ -- O(n) вместо потенциального O(1) - -### Решение - -- [ ] Заменить клоны на borrowing где возможно -- [ ] Добавить `SearchCache` с TTL -- [ ] Оптимизировать `LruCache` (использовать `VecDeque` или готовую библиотеку) - -### Файлы - -- `src/input/main_input.rs` -- `src/app/search.rs` -- `src/tdlib/users.rs` (LruCache) - ---- - -## 10. Отсутствующие архитектурные паттерны - -**Приоритет:** 🟢 Низкий -**Статус:** ❌ Не начато -**Объем:** Архитектурные изменения - -### Проблемы - -#### 10.1. Нет Event Bus - -Компоненты напрямую вызывают друг друга: -- Сложно тестировать -- Сильная связанность -- Тяжело добавлять новые фичи - -#### 10.2. Нет Repository паттерна - -Прямой доступ к данным везде: -- `app.messages.get(chat_id)` -- `app.chats.iter().find(...)` -- Нет единой точки доступа к данным - -#### 10.3. Нет Service Layer - -Бизнес-логика размазана: -- Часть в `App` -- Часть в `TdClient` -- Часть в UI handlers - -### Решение - -#### 10.1. Event Bus (опционально) - -- [ ] Создать `src/event_bus.rs` -- [ ] Pub/Sub для событий между компонентами -- [ ] Decoupling - -#### 10.2. Repository Pattern - -- [ ] Создать `src/repositories/chat_repository.rs` -- [ ] Создать `src/repositories/message_repository.rs` -- [ ] Создать `src/repositories/user_repository.rs` -- [ ] Единая точка доступа к данным - -#### 10.3. Service Layer - -- [ ] Создать `src/services/chat_service.rs` -- [ ] Создать `src/services/message_service.rs` -- [ ] Создать `src/services/search_service.rs` -- [ ] Вся бизнес-логика в сервисах - -### Файлы - -- `src/event_bus.rs` (новый, опционально) -- `src/repositories/*.rs` (новые) -- `src/services/*.rs` (новые) - ---- - -## Приоритизация - -### 🔴 Высокий приоритет (начать первым) - -1. **Дублирование кода** - быстрый win, улучшит поддерживаемость -2. **Большие файлы** - критично для навигации и понимания кода -3. **Плохая инкапсуляция** - защитит от ошибок, улучшит API - -### 🟡 Средний приоритет (после высокого) - -4. **Сложная вложенность** - улучшит читаемость -5. **Single Responsibility** - улучшит архитектуру -6. **Отсутствующие абстракции** - упростит расширение -7. **Перекрытие функциональности** - уберет путаницу - -### 🟢 Низкий приоритет (когда будет время) - -8. **Несогласованность** - косметические улучшения -9. **Производительность** - пока не critical path -10. **Архитектурные паттерны** - nice to have - ---- - -## План выполнения - -### Фаза 1: Быстрые победы (1-2 дня) - -- [x] #1: Создать утилиты для дублирующегося кода - **ЗАВЕРШЕНО** (2026-02-02) - - retry utils: 100% покрытие (все timeout заменены) - - modal_handler: интегрирован в 2 диалогах - - validation: интегрирован в 4 местах -- [ ] #5: Инкапсулировать поля App - **Частично** (геттеры добавлены) - -### Фаза 2: Разделение больших файлов (3-5 дней) - -- [ ] #2.1: Разделить `main_input.rs` -- [ ] #2.2: Разделить `client.rs` -- [ ] #2.3: Разделить `messages.rs` - -### Фаза 3: Улучшение архитектуры (5-7 дней) - -- [ ] #4: Разделить ответственности App/TdClient -- [ ] #6: Добавить абстракции (KeyHandler, network utils) -- [ ] #8: Убрать перекрытие функциональности - -### Фаза 4: Полировка (2-3 дня) - -- [x] #3: Упростить вложенность - **Частично** (main_input.rs завершён 2026-02-03) -- [ ] #7: Стандартизировать подходы -- [ ] #9: Оптимизировать производительность - -### Фаза 5: Архитектурные паттерны (опционально) - -- [ ] #10: Рассмотреть Event Bus / Repository / Service Layer - ---- - -## Метрики - -### До рефакторинга - -- Строк кода: ~15,000 -- Файлов: ~50 -- Средний размер файла: 300 строк -- Максимальный файл: 1167 строк -- Дублирование: ~15-20% -- Публичных полей в App: 22 -- Прямые вызовы timeout: 8+ - -### Текущее состояние (2026-02-04) - -- ✅ Дублирование timeout: **УСТРАНЕНО** (0 прямых вызовов, все через retry utils) -- ✅ Дублирование modal: **УСТРАНЕНО** (используется modal_handler) -- ✅ Дублирование validation: **УСТРАНЕНО** (используется validation utils) -- ✅ Вложенность в main_input.rs: **УПРОЩЕНА** (6+ уровней → 2-3 уровня) -- ✅ Размер handle() в main_input.rs: **СОКРАЩЁН** (891 строк → 82 строки, 91% сокращение) -- ✅ Размер client.rs: **СОКРАЩЁН** (1259 строк → 599 строк, 52% сокращение) -- ✅ Размер render() в ui/messages.rs: **СОКРАЩЁН** (390 строк → 92 строки, 76% сокращение) -- ✅ Размер convert_message() в tdlib/messages.rs: **СОКРАЩЁН** (150 строк → 57 строк, 62% сокращение) -- ⏳ Публичных полей в App: 22 → 21 (config приватный, геттеры добавлены) -- ✅ **Все большие функции отрефакторены!** 🎉 - -### Цели после рефакторинга - -- Максимальный файл: <500 строк -- Дублирование: <5% ✅ **ДОСТИГНУТО для категории #1!** -- Глубина вложенности: ≤3 уровня ✅ **ДОСТИГНУТО для main_input.rs!** -- Публичных полей в App: 0 -- Все файлы <400 строк (в идеале) -- Улучшенная тестируемость -- Более четкое разделение ответственностей diff --git a/REFACTORING_ROADMAP.md b/REFACTORING_ROADMAP.md deleted file mode 100644 index 09098aa..0000000 --- a/REFACTORING_ROADMAP.md +++ /dev/null @@ -1,1120 +0,0 @@ -# Refactoring Roadmap - -Этот документ содержит список технического долга и планов по рефакторингу кодовой базы. - -## Приоритет 1: Критичные улучшения - -### 1. Схлопнуть состояния чата в enum - -**Проблема**: Сейчас состояния чата хранятся как отдельные boolean поля в `App`: -```rust -is_message_selection_mode: bool, -is_editing_mode: bool, -is_reply_mode: bool, -is_forward_mode: bool, -is_delete_confirmation: bool, -is_reaction_picker_mode: bool, -is_profile_mode: bool, -is_search_in_chat_mode: bool, -``` - -**Решение**: Создать enum `ChatState`: -```rust -enum ChatState { - Normal, - MessageSelection { - selected_message_id: i64, - }, - Editing { - message_id: i64, - original_text: String, - }, - Reply { - message_id: i64, - preview_text: String, - }, - Forward { - message_id: i64, - selected_chat_index: usize, - }, - DeleteConfirmation { - message_id: i64, - }, - ReactionPicker { - message_id: i64, - available_reactions: Vec, - selected_index: usize, - }, - Profile { - info: ProfileInfo, - }, - SearchInChat { - query: String, - results: Vec, - current_index: usize, - }, -} -``` - -**Преимущества**: -- Невозможно иметь несколько состояний одновременно (type-safe) -- Проще обрабатывать переходы между состояниями -- Меньше полей в `App` -- Данные, связанные с состоянием, хранятся вместе с ним - -**Затронутые файлы**: -- `src/app/mod.rs` (добавить enum, убрать boolean поля) -- `src/input/main_input.rs` (изменить логику обработки на match) -- `src/ui/messages.rs` (изменить рендеринг на match) - ---- - -### 2. Разделить TdClient на несколько модулей - -**Проблема**: `TdClient` в `src/tdlib/client.rs` (~1500+ строк) делает слишком много: -- Авторизация -- Управление чатами -- Управление сообщениями -- Кеширование пользователей -- Реакции -- Network state - -**Решение**: Разделить на модули: -``` -src/tdlib/ -├── mod.rs # Экспорт публичных типов -├── client.rs # Основной TdClient -├── auth.rs # AuthManager -├── chats.rs # ChatManager -├── messages.rs # MessageManager -├── users.rs # UserCache -└── reactions.rs # ReactionManager -``` - -**Преимущества**: -- Принцип единственной ответственности -- Проще тестировать отдельные модули -- Легче найти и изменить код - ---- - -### 3. Вынести константы в отдельный модуль - -**Проблема**: Магические числа разбросаны по всему коду: -```rust -// В разных местах: -500 // MAX_MESSAGES_IN_CHAT -500 // MAX_USER_CACHE_SIZE -200 // MAX_CHATS -8 // Emoji picker columns -10 // Max input height -16 // Poll timeout (60 FPS) -``` - -**Решение**: Создать `src/constants.rs`: -```rust -// Memory limits -pub const MAX_MESSAGES_IN_CHAT: usize = 500; -pub const MAX_USER_CACHE_SIZE: usize = 500; -pub const MAX_CHATS: usize = 200; -pub const MAX_CHAT_USER_IDS: usize = 500; - -// UI constants -pub const EMOJI_PICKER_COLUMNS: usize = 8; -pub const EMOJI_PICKER_ROWS: usize = 6; -pub const MAX_INPUT_HEIGHT: usize = 10; -pub const MIN_TERMINAL_WIDTH: u16 = 80; -pub const MIN_TERMINAL_HEIGHT: u16 = 20; - -// Performance -pub const POLL_TIMEOUT_MS: u64 = 16; // 60 FPS -pub const SHUTDOWN_TIMEOUT_SECS: u64 = 2; -pub const LAZY_LOAD_USERS_PER_TICK: usize = 5; - -// TDLib -pub const TDLIB_CHAT_LIMIT: i32 = 50; -pub const TDLIB_MESSAGE_LIMIT: i32 = 50; -``` - -**Преимущества**: -- Единое место для всех констант -- Проще изменить значения -- Самодокументирующийся код - ---- - -## Приоритет 2: Улучшение типобезопасности - -### 4. Newtype pattern для ID ✅ ЗАВЕРШЕНО! - -**Статус**: ЗАВЕРШЕНО (2026-01-31) - -**Проблема**: Везде используется `i64` для `chat_id`, `message_id`, `user_id` — легко перепутать. - -**Решение**: ✅ Реализовано в `src/types.rs`: -```rust -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct ChatId(pub i64); - -impl ChatId { - pub fn new(id: i64) -> Self { Self(id) } - pub fn as_i64(&self) -> i64 { self.0 } -} - -impl From for ChatId { - fn from(id: i64) -> Self { ChatId(id) } -} - -impl Display for ChatId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -// Аналогично для MessageId и UserId -``` - -**Что сделано**: -- ✅ Создан `src/types.rs` с тремя типами: `ChatId`, `MessageId`, `UserId` -- ✅ Добавлены методы `new()`, `as_i64()`, `From`, `Display` -- ✅ Реализованы traits: `Hash`, `Eq`, `Serialize`, `Deserialize` -- ✅ Обновлены 15+ модулей: - - `tdlib/types.rs`, `tdlib/chats.rs`, `tdlib/messages.rs`, `tdlib/users.rs` - - `tdlib/reactions.rs`, `tdlib/client.rs` - - `app/mod.rs`, `app/chat_state.rs`, `input/main_input.rs` - - Test helpers: `app_builder.rs`, `test_data.rs` -- ✅ Исправлены 53 ошибки компиляции -- ✅ Код компилируется успешно - -**Преимущества**: -- ✅ Невозможно случайно передать message_id вместо chat_id -- ✅ Компилятор ловит ошибки на этапе компиляции -- ✅ Улучшенная читаемость кода -- ✅ Самодокументирующиеся типы - ---- - -### 5. Создать enum для ошибок - -**Проблема**: Везде используется `Result` — теряется контекст ошибок. - -**Решение**: Создать `src/error.rs`: -```rust -#[derive(Debug, thiserror::Error)] -pub enum TeletuiError { - #[error("TDLib error: {0}")] - TdLib(String), - - #[error("Configuration error: {0}")] - Config(String), - - #[error("Network error: {0}")] - Network(String), - - #[error("Authentication error: {0}")] - Auth(String), - - #[error("Invalid timezone format: {0}")] - InvalidTimezone(String), - - #[error("Invalid color: {0}")] - InvalidColor(String), - - #[error("IO error: {0}")] - Io(#[from] std::io::Error), -} - -pub type Result = std::result::Result; -``` - -**Зависимости**: `thiserror = "1.0"` - -**Преимущества**: -- Типобезопасная обработка ошибок -- Понятные сообщения об ошибках -- Возможность pattern matching - ---- - -### 6. Группировка полей MessageInfo ✅ ЗАВЕРШЕНО! - -**Статус**: ЗАВЕРШЕНО (2026-01-31) - -**Проблема**: `MessageInfo` имеет слишком много плоских полей (~15+). - -**Решение**: ✅ Реализовано - группировка в логические структуры: -```rust -pub struct MessageInfo { - pub metadata: MessageMetadata, - pub content: MessageContent, - pub state: MessageState, - pub interactions: MessageInteractions, -} - -pub struct MessageMetadata { - pub id: MessageId, - pub chat_id: ChatId, - pub sender_id: UserId, - pub date: i32, -} - -pub struct MessageContent { - pub text: String, - pub formatted_text: Option, - pub media_type: Option, -} - -pub struct MessageState { - pub is_outgoing: bool, - pub is_edited: bool, - pub is_pinned: bool, -} - -pub struct MessageInteractions { - pub reply_to_message_id: Option, - pub forward_info: Option, - pub reactions: Vec, - pub read_count: i32, -} -``` - -**Что сделано**: -- ✅ Созданы 4 структуры: MessageMetadata, MessageContent, MessageState, MessageInteractions -- ✅ Обновлена MessageInfo для использования новых структур -- ✅ Добавлен конструктор MessageInfo::new() -- ✅ Добавлены getter методы (id(), text(), sender_name(), и др.) -- ✅ Обновлены 14 файлов (~200+ обращений): - - ui/messages.rs: рендеринг (100+ изменений) - - app/mod.rs: логика приложения - - input/main_input.rs: обработка ввода - - tdlib/client.rs: обработка updates - - Все тестовые файлы -- ✅ Код компилируется успешно - -**Преимущества**: -- ✅ Логическая группировка данных -- ✅ Проще добавлять новые поля -- ✅ Улучшенная читаемость кода -- ✅ Меньше параметров в конструкторах (используется new()) - ---- - -### MessageBuilder pattern ✅ ЗАВЕРШЕНО! - -**Статус**: ЗАВЕРШЕНО (2026-01-31) - -**Проблема**: MessageInfo::new() принимает 14 параметров, что неудобно и подвержено ошибкам. - -**Решение**: ✅ Реализован MessageBuilder с fluent API: -```rust -let message = MessageBuilder::new(MessageId::new(123)) - .sender_name("Alice") - .text("Hello, world!") - .outgoing() - .read() - .build(); -``` - -**Что сделано**: -- ✅ Создана структура MessageBuilder в tdlib/types.rs -- ✅ Реализовано 16 методов fluent API: - - Базовые: sender_name, text, entities, date, edit_date - - Флаги: outgoing, incoming, read, unread, edited - - Права: editable, deletable_for_self, deletable_for_all - - Дополнительно: reply_to, forward_from, reactions, add_reaction -- ✅ Обновлён convert_message() для использования builder -- ✅ Добавлены 6 unit тестов -- ✅ Код компилируется успешно - -**Преимущества**: -- ✅ Более читабельный код -- ✅ Самодокументирующийся API -- ✅ Гибкость в установке опциональных полей -- ✅ Проще поддерживать и расширять - -**🎉 Priority 2 ЗАВЕРШЁН НА 100%! 🎉** - ---- - -## Приоритет 3: Архитектурные улучшения - -### 7. Выделить UI компоненты ✅ ЗАВЕРШЕНО! - -**Статус**: ЗАВЕРШЕНО (5/5 компонентов, 2026-02-02) - -**Проблема**: Код рендеринга дублируется, сложно переиспользовать. - -**Решение**: ✅ Создано `src/ui/components/`: -``` -src/ui/components/ -├── mod.rs ✅ -├── modal.rs ✅ (87 строк, полностью реализовано) -├── input_field.rs ✅ (54 строк, полностью реализовано) -├── message_bubble.rs ⚠️ (27 строк, placeholder, блокируется P3.8 и P3.9) -├── chat_list_item.rs ✅ (78 строк, полностью реализовано) -└── emoji_picker.rs ✅ (112 строк, полностью реализовано) -``` - -**Что сделано**: -- ✅ Создана структура модулей `src/ui/components/` -- ✅ Реализовано 5 из 5 компонентов: - - `modal.rs` — базовые модалки с центрированием (87 строк) - - `input_field.rs` — текстовое поле с курсором (54 строки) - - `chat_list_item.rs` — элемент списка чатов (78 строк) - - `emoji_picker.rs` — picker реакций (112 строк) - - `message_bubble.rs` — рендеринг сообщений (437 строк) ✅ **ЗАВЕРШЕНО 2026-02-02** -- ✅ Все компоненты используются в UI -- ✅ `messages.rs` использует `message_grouping` и компоненты - -**Преимущества**: -- ✅ Переиспользуемые компоненты -- ✅ Консистентный UI -- ✅ Проще тестировать - ---- - -### 8. Вынести форматирование в отдельный модуль - -**Проблема**: Markdown форматирование захардкожено в `messages.rs` (~200+ строк). - -**Решение**: Создать `src/formatting.rs`: -```rust -pub struct FormattedSpan { - pub text: String, - pub style: Style, -} - -pub fn format_text_entities( - text: &str, - entities: &[TextEntity], -) -> Vec { - // Вся логика форматирования -} -``` - -**Преимущества**: -- Разделение ответственности -- Можно тестировать отдельно -- Переиспользование в других местах - ---- - -### 9. Вынести логику группировки сообщений ✅ ЗАВЕРШЕНО! - -**Статус**: ЗАВЕРШЕНО (2026-01-31) - -**Проблема**: Логика группировки сообщений смешана с рендерингом в `messages.rs`. - -**Решение**: ✅ Создан `src/message_grouping.rs`: -```rust -pub enum MessageGroup { - DateSeparator(i32), - SenderHeader { is_outgoing: bool, sender_name: String }, - Message(MessageInfo), -} - -pub fn group_messages(messages: &[MessageInfo]) -> Vec { - // Логика группировки по дате и отправителю -} -``` - -**Что сделано**: -- ✅ Создан модуль `src/message_grouping.rs` (255 строк) -- ✅ Реализован enum `MessageGroup` с тремя вариантами -- ✅ Реализована функция `group_messages()` для группировки по дате и отправителю -- ✅ Добавлена полная документация с примерами -- ✅ Написано 5 unit тестов (все проходят) -- ✅ Модуль добавлен в `src/lib.rs` -- ✅ Код компилируется успешно - -**Преимущества**: -- ✅ Чистое разделение логики и представления -- ✅ Легче тестировать группировку (покрыто тестами) -- ✅ Можно переиспользовать -- ✅ Готово для интеграции в `messages.rs` - ---- - -### 10. Hotkey mapping в конфиг ✅ ЗАВЕРШЕНО! - -**Статус**: ЗАВЕРШЕНО (2026-01-31) - -**Проблема**: Хоткеи захардкожены в коде, нельзя настроить. - -**Решение**: ✅ Добавлено в `config.toml`: -```toml -[hotkeys] -# Навигация (vim + русские + стрелки) -up = ["k", "р", "Up"] -down = ["j", "о", "Down"] -left = ["h", "р", "Left"] -right = ["l", "д", "Right"] - -# Действия (англ + русские) -reply = ["r", "к"] -forward = ["f", "а"] -delete = ["d", "в", "Delete"] -copy = ["y", "н"] -react = ["e", "у"] -profile = ["i", "ш"] -``` - -**Что сделано**: -- ✅ Создана структура `HotkeysConfig` в `src/config.rs` -- ✅ Добавлены поля для всех действий (10 hotkeys) -- ✅ Реализован метод `matches(key: KeyCode, action: &str) -> bool` -- ✅ Поддержка символьных клавиш (англ + русские) -- ✅ Поддержка специальных клавиш (Up, Down, Left, Right, Delete, Enter, Esc) -- ✅ Добавлены дефолтные значения для всех hotkeys -- ✅ Написано 9 unit тестов (all passing ✅) -- ✅ Добавлена полная rustdoc документация -- ✅ Config::default() включает hotkeys - -**Примеры использования**: -```rust -let config = Config::default(); - -// Проверяем английскую клавишу -if config.hotkeys.matches(KeyCode::Char('r'), "reply") { - // Начать ответ -} - -// Проверяем русскую клавишу -if config.hotkeys.matches(KeyCode::Char('к'), "reply") { - // Начать ответ (та же логика) -} - -// Проверяем стрелку -if config.hotkeys.matches(KeyCode::Up, "up") { - // Вверх по списку -} -``` - -**Преимущества**: -- ✅ Пользовательская настройка хоткеев через config.toml -- ✅ Проще добавлять новые действия -- ✅ Документация хоткеев в конфиге -- ✅ Централизованное управление клавишами -- ✅ Поддержка русской раскладки out of the box - -**🎉 Priority 3 ЗАВЕРШЁН НА 100%! 🎉** - ---- - -## Приоритет 4: Качество кода - -### 11. Добавить юнит-тесты ✅ ЗАВЕРШЕНО! - -**Статус**: ЗАВЕРШЕНО 100% (+106 строк тестов, 2026-02-01) - -**Что сделано**: -- ✅ Добавлены 9 unit тестов в `src/utils.rs` (в секции `#[cfg(test)]`) -- ✅ Покрыты все edge cases для форматирования времени -- ✅ Тестирование приватных функций через публичный API -- ✅ Все 54 unit теста проходят (было 45, +9 новых) - -**Добавленные тесты**: -- `format_timestamp_with_tz` - положительный offset (+03:00) -- `format_timestamp_with_tz` - отрицательный offset (-05:00) -- `format_timestamp_with_tz` - нулевой offset (UTC) -- `format_timestamp_with_tz` - переход через полночь -- `format_timestamp_with_tz` - невалидный timezone (fallback) -- `get_day` - расчет дня из timestamp -- `get_day_grouping` - группировка сообщений по дням -- `format_datetime` - полная дата и время с MSK -- `parse_timezone_offset` - через публичный API (приватная функция) - -**Примеры**: -```rust -#[test] -fn test_format_timestamp_with_tz_positive_offset() { - let timestamp = 1640000000; // 2021-12-20 11:33:20 UTC - assert_eq!(format_timestamp_with_tz(timestamp, "+03:00"), "14:33"); -} - -#[test] -fn test_get_day_grouping() { - let msg1 = 1640000000; // 2021-12-20 09:33:20 - let msg2 = 1640040000; // 2021-12-20 20:40:00 - assert_eq!(get_day(msg1), get_day(msg2)); // Один день -} -``` - -**Запуск**: `cargo test --lib utils::tests` - ---- - -### 12. Добавить rustdoc комментарии ✅ ЗАВЕРШЕНО! - -**Статус**: ЗАВЕРШЕНО 100% (+900 строк документации, 2026-02-01) - -**Что сделано**: -- ✅ Документированы все TDLib модули (auth, chats, messages, reactions, users) -- ✅ Документированы все публичные структуры и методы -- ✅ Добавлены примеры использования (34 doctests) -- ✅ Документация для Config и утилит (formatting) -- ✅ Все doctests работают (30 ignored для async, 4 compiled) - -**Модули с документацией**: -- `src/tdlib/auth.rs` - AuthManager, AuthState (6 doctests) -- `src/tdlib/chats.rs` - ChatManager (8 doctests) -- `src/tdlib/messages.rs` - MessageManager, 14 методов (6 doctests) -- `src/tdlib/reactions.rs` - ReactionManager (3 doctests) -- `src/tdlib/users.rs` - UserCache, LruCache (2 doctests) -- `src/config.rs` - Config, ColorsConfig, GeneralConfig (4 doctests) -- `src/formatting.rs` - Форматирование текста (2 doctests) -- `src/tdlib/client.rs` - TdClient (1 doctest) -- `src/app/mod.rs` - App (1 doctest) -- `src/message_grouping.rs` - Группировка (1 doctest) -- `src/tdlib/types.rs` - MessageBuilder (1 doctest) - -**Примеры**: -```rust -/// Менеджер авторизации TDLib. -/// -/// # Examples -/// -/// ```ignore -/// let mut auth_manager = AuthManager::new(client_id); -/// auth_manager.send_phone_number("+1234567890".to_string()).await?; -/// auth_manager.send_code("12345".to_string()).await?; -/// ``` -pub struct AuthManager { ... } -``` - -**Генерация**: `cargo doc --open` - ---- - -### 13. Config валидация ✅ ЗАВЕРШЕНО! - -**Статус**: ЗАВЕРШЕНО 100% (+149 строк тестов, 2026-02-01) - -**Что сделано**: -- ✅ Валидация уже была реализована в `config.rs:344-389` -- ✅ Вызов валидации в `Config::load():450-456` -- ✅ Добавлено 15 comprehensive тестов для полного покрытия -- ✅ Все 23 config теста проходят (8 существующих + 15 новых) - -**Добавленные тесты**: -- Валидация дефолтного конфига -- Timezone: валидный (+03:00, -05:00), невалидный (без знака) -- Цвета: все 18 стандартных ratatui цветов -- Невалидные цвета (rainbow, purple, pink) -- Case-insensitive парсинг (RED, Green, YELLOW) -- parse_color() для всех вариантов (standard, light, gray/grey) -- Fallback к White для невалидных цветов - -**Реализация**: Уже была добавлена ранее: -```rust -impl Config { - pub fn validate(&self) -> Result<(), TeletuiError> { - // Проверка timezone - if !self.general.timezone.starts_with('+') - && !self.general.timezone.starts_with('-') { - return Err(TeletuiError::InvalidTimezone( - format!("Timezone must start with + or -: {}", self.general.timezone) - )); - } - - // Проверка цветов - let valid_colors = [ - "black", "red", "green", "yellow", "blue", "magenta", - "cyan", "gray", "white", "darkgray", "lightred", - "lightgreen", "lightyellow", "lightblue", - "lightmagenta", "lightcyan" - ]; - - for color_name in [ - &self.colors.incoming_message, - &self.colors.outgoing_message, - &self.colors.selected_message, - &self.colors.reaction_chosen, - &self.colors.reaction_other, - ] { - if !valid_colors.contains(&color_name.to_lowercase().as_str()) { - return Err(TeletuiError::InvalidColor( - format!("Unknown color: {}", color_name) - )); - } - } - - Ok(()) - } -} -``` - -Вызывать при загрузке: -```rust -pub fn load() -> Self { - let config = // ... загрузка из файла - if let Err(e) = config.validate() { - eprintln!("Config validation error: {}", e); - return Self::default(); - } - config -} -``` - ---- - -### 14. Async/await консистентность ✅ ЗАВЕРШЕНО! - -**Статус**: ЗАВЕРШЕНО 100% (проверка кода, 2026-02-01) - -**Проверка показала**: Код уже соответствует требованиям! - -**Что проверено**: -- ✅ `std::fs` используется только в `Config::load()` при старте (не в async runtime) -- ✅ `std::thread::sleep` - не найдено ни разу -- ✅ `tokio::time::sleep` используется в async функциях (messages.rs) -- ✅ `tokio::time::timeout` используется (auth.rs, main_input.rs, main.rs) -- ✅ Все файловые операции вызываются синхронно при инициализации - -**Детали**: -```rust -// ✓ ПРАВИЛЬНО: Config::load() при старте, перед async runtime -#[tokio::main] -async fn main() -> Result<(), io::Error> { - let config = config::Config::load(); // Синхронно, при инициализации - // ... async runtime начинается позже -} - -// ✓ ПРАВИЛЬНО: tokio::time::sleep в async функциях -async fn load_messages() { - use tokio::time::{sleep, Duration}; - sleep(Duration::from_millis(100)).await; // Не блокирует -} -``` - -**Вывод**: Блокирующих вызовов в async контексте нет. Код async-clean. - ---- - -## Приоритет 5: Опциональные улучшения - -### 15. Feature flags для зависимостей ✅ ЗАВЕРШЕНО - -**Проблема**: Все зависимости всегда включены. - -**Решение**: В `Cargo.toml`: -```toml -[features] -default = ["clipboard", "url-open"] -clipboard = ["dep:arboard"] -url-open = ["dep:open"] - -[dependencies] -arboard = { version = "3.4", optional = true } -open = { version = "5.0", optional = true } -``` - -**Реализовано**: -- ✅ Добавлены feature flags в Cargo.toml -- ✅ Зависимости `arboard` и `open` сделаны опциональными -- ✅ Условная компиляция в `src/input/main_input.rs`: - - `#[cfg(feature = "url-open")]` для `open::that()` - - `#[cfg(feature = "clipboard")]` / `#[cfg(not(feature = "clipboard"))]` для `copy_to_clipboard()` -- ✅ Условная компиляция в `tests/copy.rs`: - - `#[cfg(all(test, feature = "clipboard"))]` для clipboard тестов - -**Преимущества**: -- ✅ Уменьшение размера бинарника -- ✅ Опциональная функциональность -- ✅ Graceful degradation при отключении фич - ---- - -### 16. LRU cache обобщение ✅ ЗАВЕРШЕНО - -**Проблема**: Отдельные LRU кеши для `user_names` и `user_statuses`. - -**Решение**: Создать обобщённый `LruCache` или использовать готовый крейт `lru = "0.12"`. - -**Реализовано**: -- ✅ Обобщённая структура `LruCache` в `src/tdlib/users.rs` -- ✅ Type parameters: - - `K: Eq + Hash + Clone + Copy` — тип ключа - - `V: Clone` — тип значения -- ✅ Обновлена `UserCache`: - - `user_usernames: LruCache` - - `user_names: LruCache` - - `user_statuses: LruCache` -- ✅ Все методы обобщены: `get()`, `peek()`, `insert()`, `contains_key()`, `len()` - -**Преимущества**: -- ✅ Переиспользуемая реализация для любых типов ключей -- ✅ Type-safe кеширование -- ✅ Без дополнительных зависимостей - ---- - -### 17. Tracing вместо println! ✅ ЗАВЕРШЕНО - -**Проблема**: Используется `eprintln!` для логов. - -**Решение**: Использовать `tracing`: -```rust -use tracing::{info, warn, error, debug}; - -// Вместо -eprintln!("Warning: Could not load config: {}", e); - -// Использовать -warn!("Could not load config: {}", e); -``` - -**Реализовано**: -- ✅ Добавлены зависимости в `Cargo.toml`: - - `tracing = "0.1"` - - `tracing-subscriber = { version = "0.3", features = ["env-filter"] }` -- ✅ Инициализирован subscriber в `main.rs`: - - Уровень логов по умолчанию: `warn` - - Настраивается через переменную окружения `RUST_LOG` -- ✅ Заменены все `eprintln!` на tracing макросы в `src/config.rs`: - - 4× `warn!()` для предупреждений - - 1× `error!()` для ошибок валидации - - 1× `warn!()` для fallback на дефолтную конфигурацию - -**Преимущества**: -- ✅ Структурированное логирование -- ✅ Настраиваемые уровни логов (через `RUST_LOG`) -- ✅ Лучшая интеграция с async кодом -- ✅ Единый подход к логированию во всём проекте - ---- - -## Метрики прогресса - -- [x] Priority 1: 3/3 задач ✅ ЗАВЕРШЕНО! - - [x] P1.1 — ChatState enum - - [x] P1.2 — Разделить TdClient - - [x] P1.3 — Константы -- [x] Priority 2: 5/5 задач ✅ ЗАВЕРШЕНО! 🎉 - - [x] P2.5 — Error enum - - [x] P2.3 — Config validation - - [x] P2.4 — Newtype для ID - - [x] P2.6 — MessageInfo реструктуризация - - [x] P2.7 — MessageBuilder pattern -- [x] Priority 3: 4/4 задач ✅ ЗАВЕРШЕНО! 🎉🎉 - - [x] P3.7 — UI компоненты (5/5) ✅ ПОЛНОСТЬЮ ЗАВЕРШЕНО 2026-02-02! - - [x] P3.8 — Formatting модуль ✅ - - [x] P3.9 — Message Grouping ✅ - - [x] P3.10 — Hotkey Mapping ✅ -- [x] Priority 4: 4/4 задач ✅ ЗАВЕРШЕНО! 🎉🎉🎉 - - [x] P4.11 — Unit tests ✅ - - [x] P4.12 — Rustdoc ✅ - - [x] P4.13 — Config validation ✅ - - [x] P4.14 — Async/await consistency ✅ -- [x] Priority 5: 3/3 задач ✅ ЗАВЕРШЕНО! 🎉🎉🎉 - - [x] P5.15 — Feature flags ✅ - - [x] P5.16 — LRU cache обобщение ✅ - - [x] P5.17 — Tracing ✅ -- [x] Priority 6: 1/1 задач ✅ ЗАВЕРШЕНО! 🎉🎉🎉🎉 - - [x] P6.1 — Dependency Injection для TdClient (ВСЕ 8 этапов завершены!) - -**Всего**: 21/21 задач (100%) 🎊🎉 РЕФАКТОРИНГ ПОЛНОСТЬЮ ЗАВЕРШЁН! - ---- - -## Предусловие: Тесты - -**ВАЖНО**: Перед началом рефакторинга необходимо написать тесты! - -См. [TESTING_ROADMAP.md](TESTING_ROADMAP.md) для плана покрытия тестами. - -Минимальное покрытие для начала рефакторинга: -- ✅ Фаза 0: Инфраструктура (helpers, fake client) -- ✅ Snapshot тесты для основных экранов (chat list, messages) -- ✅ Integration тесты для критичных flow (send, edit, navigation) - -**Зачем**: Тесты гарантируют, что рефакторинг не сломает функциональность. - ---- - -## Порядок выполнения - -Рекомендуется выполнять в следующем порядке: - -1. **P1.3** — Константы (быстро, малый риск) -2. **P1.1** — ChatState enum (высокий impact) -3. **P2.5** — Error enum (улучшает весь код) -4. **P4.11** — Тесты для utils (базовая проверка) -5. **P1.2** — Разделить TdClient (большой рефакторинг) -6. **P2.4** — Newtype для ID (широкие изменения) -7. **P3.7** — UI компоненты (постепенно) -8. **P3.8** — Форматирование (изоляция логики) -9. **P3.9** — Группировка сообщений (изоляция логики) -10. Остальные по необходимости - ---- - -## Принципы рефакторинга - -1. **Один PR = одна задача** — не смешивать рефакторинг разных областей -2. **Тесты прежде всего** — добавить тесты перед рефакторингом -3. **Обратная совместимость** — сохранять работоспособность на каждом шаге -4. **Маленькие шаги** — лучше 10 маленьких PR, чем 1 огромный -5. **Документация** — обновлять документацию после изменений - ---- - -## Приоритет 6: Улучшение тестируемости - -### P6.1 — Dependency Injection для TdClient ✅ ЗАВЕРШЕНО! - -**Статус**: ✅ ЗАВЕРШЕНО (ВСЕ 8 этапов завершены!) - 2026-02-02 - -**Проблема**: - -В текущей реализации тесты создают **настоящий** `TdClient`, который вызывает `tdlib_rs::create_client()`. Это приводит к: -1. **Зависанию тестов** — TDLib не инициализирован и блокирует async вызовы -2. **Verbose логи** — TDLib выводит много логов при создании клиента -3. **Медленные тесты** — создание TDLib клиента занимает время -4. **Хаки в продакшн коде** — пришлось добавить `tokio::time::timeout(100ms)` для всех вызовов TDLib чтобы тесты не зависали - -**Проблемные места** (src/input/main_input.rs): -```rust -// Строка 867-870: timeout для send_chat_action при вводе символов -let _ = tokio::time::timeout( - Duration::from_millis(100), - app.td_client.send_chat_action(ChatId::new(chat_id), ChatAction::Typing) -).await; - -// Строка 683-686: timeout для set_draft_message при закрытии чата -let _ = tokio::time::timeout( - Duration::from_millis(100), - app.td_client.set_draft_message(chat_id, draft_text) -).await; - -// Строка 592-594: timeout для send_chat_action Cancel при отправке сообщения -let _ = tokio::time::timeout( - Duration::from_millis(100), - app.td_client.send_chat_action(ChatId::new(chat_id), ChatAction::Cancel) -).await; -``` - -**Решения**: - -#### Вариант 1: Trait-based Dependency Injection (рекомендуется) - -Создать trait `TdClientTrait` и сделать `App` generic: - -```rust -// src/tdlib/trait.rs -#[async_trait] -pub trait TdClientTrait { - async fn send_chat_action(&self, chat_id: ChatId, action: ChatAction); - async fn set_draft_message(&self, chat_id: ChatId, text: String) -> Result<()>; - async fn get_chat_history(&mut self, chat_id: ChatId, limit: i32) -> Result>; - async fn send_message(&mut self, chat_id: ChatId, text: String, reply_to: Option, reply_info: Option) -> Result; - async fn edit_message(&mut self, chat_id: ChatId, message_id: MessageId, text: String) -> Result; - async fn delete_messages(&mut self, chat_id: ChatId, message_ids: Vec, revoke: bool) -> Result<()>; - async fn forward_messages(&mut self, to_chat_id: ChatId, from_chat_id: ChatId, message_ids: Vec) -> Result<()>; - async fn toggle_reaction(&self, chat_id: ChatId, message_id: MessageId, emoji: String) -> Result<()>; - async fn get_message_available_reactions(&self, chat_id: ChatId, message_id: MessageId) -> Result>; - async fn search_messages(&self, chat_id: ChatId, query: &str) -> Result>; - async fn get_profile_info(&self, chat_id: ChatId) -> Result; - async fn leave_chat(&self, chat_id: ChatId) -> Result<()>; - async fn load_chats(&mut self, limit: usize) -> Result, String>; - async fn load_folder_chats(&mut self, folder_id: i32, limit: usize) -> Result<(), String>; - async fn get_pinned_messages(&self, chat_id: ChatId) -> Result, String>; - async fn load_current_pinned_message(&mut self, chat_id: ChatId); - async fn fetch_missing_reply_info(&mut self); - // ... все остальные методы - - // Синхронные методы - fn current_chat_messages(&self) -> &[MessageInfo]; - fn current_chat_messages_mut(&mut self) -> &mut Vec; - fn set_current_chat_id(&mut self, chat_id: Option); - fn folders(&self) -> &[FolderInfo]; - fn network_state(&self) -> NetworkState; - fn typing_status(&self) -> Option<(i64, String)>; - fn current_pinned_message(&self) -> Option<&MessageInfo>; - fn push_message(&mut self, message: MessageInfo); - fn set_typing_status(&mut self, status: Option<(i64, String)>); - fn set_current_pinned_message(&mut self, message: Option); -} - -// Real implementation -#[async_trait] -impl TdClientTrait for TdClient { - // Реализация всех методов, делегируя к существующим - async fn send_chat_action(&self, chat_id: ChatId, action: ChatAction) { - self.send_chat_action(chat_id, action).await - } - // ... остальные методы -} - -// Fake implementation для тестов -#[async_trait] -impl TdClientTrait for FakeTdClient { - // Реализация для тестов (уже есть в tests/helpers/fake_tdclient.rs) - async fn send_chat_action(&self, chat_id: ChatId, action: ChatAction) { - self.chat_actions.lock().unwrap().push((chat_id.as_i64(), action.to_string())); - } - // ... остальные методы -} - -// App становится generic -pub struct App { - pub td_client: T, - pub config: Config, - // ... остальные поля -} - -impl App { - pub fn new(config: Config, td_client: T) -> Self { - // ... - } - // ... все остальные методы -} - -// Специализация для продакшена -impl App { - pub fn new_default(config: Config) -> Self { - Self::new(config, TdClient::new()) - } -} - -// TestAppBuilder для тестов -impl TestAppBuilder { - pub fn build(self) -> App { - let td_client = FakeTdClient::new() - .with_chats(self.chats) - .with_messages(self.selected_chat_id.unwrap_or(0), self.messages); - - App::new(self.config, td_client) - } -} -``` - -**Плюсы**: -- ✅ Чистая архитектура, настоящий dependency injection -- ✅ Тесты не создают реальный TDLib — **быстрые и тихие** -- ✅ Убираем timeout'ы из продакшн кода — **чистота** -- ✅ Легко мокировать для unit-тестов -- ✅ Соответствует принципам SOLID (Dependency Inversion) - -**Минусы**: -- ❌ Большой рефакторинг (~50+ файлов) -- ❌ Усложнение кода (generics везде: `App`, `handle_input`) -- ❌ Потеря простоты для небольшого проекта -- ❌ Нужна библиотека `async-trait` для async методов в trait - -**Затронутые файлы**: -- `src/tdlib/trait.rs` (новый) — trait определение -- `src/tdlib/client.rs` — impl TdClientTrait for TdClient -- `src/tdlib/mod.rs` — экспорт trait -- `src/app/mod.rs` — App -- `src/input/main_input.rs` — функции становятся generic -- `src/input/auth.rs` — функции становятся generic -- `src/ui/*.rs` — функции рендеринга становятся generic -- `src/main.rs` — использовать App -- `tests/helpers/fake_tdclient.rs` — impl TdClientTrait for FakeTdClient -- `tests/helpers/app_builder.rs` — build() возвращает App -- Все интеграционные тесты (~15 файлов) - -**Оценка трудозатрат**: ~2-3 дня работы - ---- - -#### Вариант 2: Enum Dispatch (компромисс) - -```rust -// src/tdlib/wrapper.rs -pub enum TdClientWrapper { - Real(TdClient), - Fake(FakeTdClient), -} - -impl TdClientWrapper { - async fn send_chat_action(&self, chat_id: ChatId, action: ChatAction) { - match self { - Self::Real(c) => c.send_chat_action(chat_id, action).await, - Self::Fake(c) => c.send_chat_action(chat_id, action).await, - } - } - // ... все остальные методы с match на обе ветки -} - -// App использует wrapper -pub struct App { - pub td_client: TdClientWrapper, - // ... -} -``` - -**Плюсы**: -- ✅ Меньше изменений чем trait (нет generics) -- ✅ Тесты используют Fake -- ✅ Проще понять чем trait + generics - -**Минусы**: -- ❌ Всё равно много boilerplate (каждый метод требует match) -- ❌ Runtime dispatch overhead (небольшой) -- ❌ Не такой чистый как trait -- ❌ В продакшене всегда Real, но проверка match всё равно есть - -**Затронутые файлы**: ~20-30 файлов (меньше чем Вариант 1) - -**Оценка трудозатрат**: ~1 день работы - ---- - -#### Вариант 3: Оставить как есть (текущее состояние) - -**Обоснование**: -- Timeout'ы — это не "хак", а **защита от зависания UI** -- Даже в продакшене UI не должен зависать если TDLib глючит -- 100ms timeout на typing action и draft — нормально, это не критичные операции -- Защищает от deadlock'ов и network issues -- Простота важнее для небольшого проекта - -**Плюсы**: -- ✅ Нет дополнительной работы -- ✅ Код остаётся простым -- ✅ Timeout'ы улучшают надёжность даже в продакшене -- ✅ Тесты работают (хоть и создают TDLib) - -**Минусы**: -- ⚠️ Verbose логи TDLib в тестах (можно игнорировать) -- ⚠️ Тесты чуть медленнее (~0.1s на тест из-за инициализации TDLib) -- ⚠️ Timeout'ы в продакшн коде (но это не обязательно плохо) - ---- - -**Рекомендация**: - -- **Для прототипа/MVP**: Вариант 3 (текущее состояние) ✅ -- **Для production-ready проекта**: Вариант 1 (trait injection) ⭐ -- **Для быстрого улучшения**: Вариант 2 (enum dispatch) - -**Финальное решение** (2026-02-02): Реализован **Вариант 1 (trait injection)** ✅🎉 - -После завершения всех 8 этапов рефакторинга: -- ✅ Создан `TdClientTrait` с 40+ методами -- ✅ Реализован trait для `TdClient` и `FakeTdClient` -- ✅ `App` стал generic: `App` -- ✅ Все UI и input handlers обновлены на generic -- ✅ Тесты используют `FakeTdClient` (быстро, без логов TDLib) -- ✅ Продакшн использует `TdClient` (реальный TDLib) -- ✅ Timeout'ы убраны из продакшн кода -- ✅ Исправлен stack overflow в 6 методах trait реализации -- ✅ Все 196+ тестов проходят - -**Преимущества реализации**: -- 🛡️ Чистая архитектура без timeout хаков -- ⚡ Быстрые тесты (FakeTdClient работает мгновенно) -- 📝 Нет verbose логов TDLib в тестах -- 🔧 Type-safe dependency injection -- 🎯 Легко добавлять новые реализации trait - ---- - -## Примечания - -- Этот документ живой и будет обновляться -- Новые пункты добавляются по мере обнаружения -- После завершения задачи отмечать в метриках -- При появлении блокеров — документировать в соответствующей секции diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 8b8d966..0000000 --- a/SECURITY.md +++ /dev/null @@ -1,64 +0,0 @@ -# Security Policy - -## Поддерживаемые версии - -| Версия | Поддержка | -| ------ | ------------------ | -| 0.1.x | :white_check_mark: | - -## Сообщить об уязвимости - -Если вы обнаружили уязвимость в безопасности, пожалуйста: - -1. **НЕ создавайте публичный issue** -2. Отправьте email на: [security@example.com] -3. Опишите: - - Тип уязвимости - - Шаги для воспроизведения - - Потенциальное влияние - - Предложения по исправлению (если есть) - -## Безопасность credentials - -### Важно - -- **НИКОГДА** не коммитьте файлы с credentials в git -- Файлы `credentials` и `.env` уже добавлены в `.gitignore` -- TDLib сессия в `tdlib_data/` содержит токены авторизации — НЕ делитесь этой папкой - -### Рекомендации - -1. **Используйте XDG config directory**: - - Храните credentials в `~/.config/tele-tui/credentials` - - Установите права доступа: `chmod 600 ~/.config/tele-tui/credentials` - -2. **Защита сессии**: - - Папка `tdlib_data/` содержит вашу авторизованную сессию - - Не копируйте её на другие машины - - При компрометации — удалите папку и авторизуйтесь заново - -3. **Двухфакторная аутентификация**: - - Настоятельно рекомендуется включить 2FA в Telegram - - Это защитит ваш аккаунт даже при утечке API credentials - -## Известные ограничения - -### TDLib -- Приложение использует официальную библиотеку TDLib от Telegram -- Безопасность зависит от актуальности TDLib (автообновление через tdlib-rs) - -### Конфигурация -- Конфигурационный файл `config.toml` НЕ содержит чувствительных данных -- Credentials хранятся отдельно в файле `credentials` - -## Обновления безопасности - -Мы оперативно реагируем на сообщения об уязвимостях и выпускаем патчи в течение: -- **Критические**: 24-48 часов -- **Высокие**: 3-7 дней -- **Средние**: 2-4 недели -- **Низкие**: включаются в следующий релиз - -## Спасибо - -Мы ценим ваш вклад в безопасность проекта! diff --git a/TESTING_PROGRESS.md b/TESTING_PROGRESS.md deleted file mode 100644 index 7299fcb..0000000 --- a/TESTING_PROGRESS.md +++ /dev/null @@ -1,571 +0,0 @@ -# Testing Progress Report - -## Текущий статус: ВСЕ ТЕСТЫ ЗАВЕРШЕНЫ! 🎉🎊🚀 - -Все UI snapshot тесты и все integration тесты готовы! Превзошли план! - -Дата: 2026-01-30 (обновлено #6 — ФИНАЛ) - ---- - -## ✅ Что сделано - -### Phase 2: Integration Tests (99%) 🔥 - -**Всего:** 73 integration теста из 74 запланированных - -#### Phase 2.1: Send Message Flow (100%) ✅ -**Файл**: `tests/send_message.rs` (6 тестов) - -- ✅ Отправка текстового сообщения -- ✅ Отправка нескольких сообщений обновляет список -- ✅ Отправка с markdown форматированием -- ✅ Отправка в разные чаты -- ✅ Получение входящего сообщения -- ✅ Отправка с reply - -#### Phase 2.2: Edit Message Flow (100%) ✅ -**Файл**: `tests/edit_message.rs` (6 тестов) - -- ✅ Редактирование текста сообщения -- ✅ Установка edit_date после редактирования -- ✅ Проверка can_be_edited перед редактированием -- ✅ Редактирование только своих сообщений -- ✅ Множественные редактирования -- ✅ Редактирование с форматированием - -#### Phase 2.3: Delete Message Flow (100%) ✅ -**Файл**: `tests/delete_message.rs` (6 тестов) - -- ✅ Удаление сообщения из списка -- ✅ Множественные удаления -- ✅ Проверка can_be_deleted -- ✅ Удаление только своих сообщений -- ✅ Удаление из разных чатов -- ✅ Delete with revoke - -#### Phase 2.4: Reply & Forward Flow (100%) ✅ -**Файл**: `tests/reply_forward.rs` (8 тестов) - -- ✅ Reply на сообщение с превью -- ✅ Reply сохраняет связь с оригиналом -- ✅ Forward сообщения -- ✅ Forward с sender_name -- ✅ Forward в разные чаты -- ✅ Reply + Forward комбо -- ✅ Reply на forwarded сообщение -- ✅ Forward reply сообщения - -#### Phase 2.5: Reactions Flow (100%) ✅ -**Файл**: `tests/reactions.rs` (10 тестов) - -- ✅ Добавление реакции на сообщение -- ✅ Удаление реакции (toggle) -- ✅ Множественные реакции на одно сообщение -- ✅ Реакции от разных пользователей -- ✅ Подсчёт реакций -- ✅ Chosen реакция (своя) -- ✅ Реакции обновляются в реальном времени -- ✅ Получение доступных реакций чата -- ✅ Реакции на forwarded сообщения -- ✅ Очистка всех реакций - -#### Phase 2.6: Search Flow (100%) ✅ -**Файл**: `tests/search.rs` (8 тестов) - -- ✅ Поиск по названию чата -- ✅ Поиск по @username -- ✅ Поиск по сообщениям в чате -- ✅ Навигация по результатам поиска -- ✅ Case-insensitive поиск -- ✅ Поиск с пробелами -- ✅ Поиск возвращает пустой список если нет совпадений -- ✅ Очистка поиска - -#### Phase 2.7: Drafts Flow (100%) ✅ -**Файл**: `tests/drafts.rs` (7 тестов) - -- ✅ Сохранение черновика при переключении чатов -- ✅ Восстановление черновика при возврате -- ✅ Удаление черновика после отправки -- ✅ Черновики для разных чатов независимы -- ✅ Индикатор черновика в списке чатов -- ✅ Пустой черновик не сохраняется -- ✅ Черновик сохраняется при закрытии чата - -#### Phase 2.8: Navigation Flow (100%) ✅ -**Файл**: `tests/navigation.rs` (7 тестов) - -- ✅ Навигация по списку чатов (↑/↓) -- ✅ Открытие чата (Enter) -- ✅ Закрытие чата (Esc) -- ✅ Скролл сообщений (↑/↓) -- ✅ Переключение между папками (1-9) -- ✅ Навигация с wrap (переход с конца на начало) -- ✅ Навигация в пустом списке - -#### Phase 2.9: Profile Flow (100%) ✅ -**Файл**: `tests/profile.rs` (6 тестов) - -- ✅ Открытие профиля личного чата -- ✅ Профиль показывает имя и username -- ✅ Профиль показывает телефон -- ✅ Открытие профиля группы -- ✅ Профиль группы показывает участников -- ✅ Закрытие профиля (Esc) - -#### Phase 2.10: Network & Typing Flow (100%) ✅ -**Файл**: `tests/network_typing.rs` (9 тестов) - -- ✅ Typing indicator при наборе текста -- ✅ Отправка typing action -- ✅ Получение typing статуса -- ✅ Typing timeout -- ✅ Network state: WaitingForNetwork -- ✅ Network state: ConnectingToProxy -- ✅ Network state: Connecting -- ✅ Network state: Updating -- ✅ Network state: Ready - -#### Phase 2.11: Copy Flow (100%) ✅ -**Файл**: `tests/copy.rs` (9 тестов) - -- ✅ Форматирование простого сообщения -- ✅ Форматирование с forward контекстом -- ✅ Форматирование с reply контекстом -- ✅ Форматирование с forward + reply одновременно -- ✅ Форматирование длинного сообщения -- ✅ Форматирование с markdown entities -- ✅ Clipboard initialization (игнорируется в CI) -- ✅ Копирование в реальный clipboard (ручное тестирование) -- ✅ Кроссплатформенность clipboard - -#### Phase 2.12: Config Flow (100%) ✅ -**Файл**: `tests/config.rs` (11 тестов) - -- ✅ Дефолтные значения конфигурации -- ✅ Кастомные значения конфигурации -- ✅ Парсинг валидных цветов (red, green, blue, etc.) -- ✅ Парсинг light цветов (lightred, lightgreen, etc.) -- ✅ Парсинг невалидного цвета с fallback на White -- ✅ Case-insensitive парсинг цветов -- ✅ TOML сериализация и десериализация -- ✅ Частичный TOML использует дефолты -- ✅ Различные форматы timezone (+03:00, -05:00, +00:00) -- ✅ Загрузка credentials из переменных окружения -- ✅ Проверка формата ошибки когда credentials не найдены - ---- - -### Фаза 1: UI Snapshot Tests (100%) ✅ - -**Всего:** 55 snapshot тестов - -#### Фаза 1.1: Chat List (100%) ✅ -**Файл**: `tests/chat_list.rs` (9 тестов) - -#### Фаза 1.2: Messages (100%) ✅ -**Файл**: `tests/messages.rs` (18 тестов) - -#### Фаза 1.3: Modals (100%) ✅ -**Файл**: `tests/modals.rs` (8 тестов) - -#### Фаза 1.4: Input Field (100%) ✅ -**Файл**: `tests/input_field.rs` (7 тестов) - -#### Snapshot тесты для поля ввода: -- ✅ `snapshot_empty_input` — пустое поле ввода с плейсхолдером -- ✅ `snapshot_input_with_text` — поле с текстом и курсором █ -- ✅ `snapshot_input_long_text_2_lines` — длинный текст на 2 строки -- ✅ `snapshot_input_long_text_max_lines` — очень длинный текст (максимум 10 строк) -- ✅ `snapshot_input_editing_mode` — режим редактирования с превью оригинального сообщения -- ✅ `snapshot_input_reply_mode` — режим ответа с превью сообщения -- ✅ `snapshot_input_search_mode` — поле поиска с query - -#### Результаты: -- **7 новых snapshot тестов** — все проходят ✅ -- **7 snapshots приняты** через `cargo insta accept` -- **Все тесты проходят**: 90 тестов (21 chat_list + 19 input_field + 30 messages + 20 modals) - ---- - -### Фаза 1.6: Screens Snapshot Tests (100%) ✅ - -**Файл**: `tests/screens.rs` (7 тестов) - -#### Snapshot тесты для полных экранов: -- ✅ `snapshot_loading_screen_default` — экран загрузки (дефолтный) -- ✅ `snapshot_loading_screen_with_status` — экран загрузки со статусом -- ✅ `snapshot_auth_screen_phone` — экран авторизации (ввод телефона) -- ✅ `snapshot_auth_screen_code` — экран авторизации (ввод кода) -- ✅ `snapshot_auth_screen_password` — экран авторизации (ввод пароля 2FA) -- ✅ `snapshot_main_screen_empty` — главный экран (пустой список чатов) -- ✅ `snapshot_main_screen_terminal_too_small` — предупреждение о маленьком терминале - -#### Обновления TestAppBuilder: -- ✅ Добавлен метод `status_message(message)` — установить статус для loading screen -- ✅ Добавлен метод `auth_state(state)` — установить состояние авторизации -- ✅ Добавлен метод `phone_input(phone)` — установить phone input -- ✅ Добавлен метод `code_input(code)` — установить code input -- ✅ Добавлен метод `password_input(password)` — установить password input -- ✅ Добавлены поля: `status_message`, `auth_state`, `phone_input`, `code_input`, `password_input` -- ✅ Обновлен `build()` — применяет auth состояние и inputs - -#### Результаты: -- **7 новых snapshot тестов** — все проходят ✅ -- **7 snapshots приняты** через `cargo insta accept` -- **Все тесты проходят**: 127 тестов (21 chat_list + 19 input_field + 30 messages + 20 modals + 18 footer + 19 screens) - ---- - -### Фаза 1.5: Footer Snapshot Tests (100%) ✅ - -**Файл**: `tests/footer.rs` (6 тестов) - -#### Snapshot тесты для нижней панели: -- ✅ `snapshot_footer_chat_list` — footer в списке чатов -- ✅ `snapshot_footer_open_chat` — footer в открытом чате -- ✅ `snapshot_footer_network_waiting` — footer с "⚠ Нет сети" -- ✅ `snapshot_footer_network_connecting_proxy` — footer с "⏳ Прокси..." -- ✅ `snapshot_footer_network_connecting` — footer с "⏳ Подключение..." -- ✅ `snapshot_footer_search_mode` — footer в режиме поиска - -#### Изменения: -- ✅ Сделан `footer` модуль публичным в `src/ui/mod.rs` - -#### Результаты: -- **6 новых snapshot тестов** — все проходят ✅ -- **6 snapshots приняты** через `cargo insta accept` -- **Все тесты проходят**: 96 тестов (21 chat_list + 19 input_field + 30 messages + 20 modals + 18 footer) - ---- - -### Фаза 1.4: Input Field Snapshot Tests (100%) ✅ - -**Файл**: `tests/modals.rs` (8 тестов) - -#### Snapshot тесты для модальных окон: -- ✅ `snapshot_delete_confirmation_modal` — модалка подтверждения удаления -- ✅ `snapshot_emoji_picker_default` — emoji picker с дефолтным выбором -- ✅ `snapshot_emoji_picker_with_selection` — emoji picker с выбранной реакцией (курсор) -- ✅ `snapshot_profile_personal_chat` — профиль личного чата -- ✅ `snapshot_profile_group_chat` — профиль группы (с участниками) -- ✅ `snapshot_pinned_message` — закреплённое сообщение вверху чата -- ✅ `snapshot_search_in_chat` — поиск в чате с результатами -- ✅ `snapshot_forward_mode` — режим пересылки (выбор чата) - -#### Обновления TestAppBuilder: -- ✅ Добавлен метод `with_chats(chats)` — добавить несколько чатов сразу -- ✅ Добавлен метод `message_search(query)` — режим поиска по сообщениям -- ✅ Добавлен метод `forward_mode(message_id)` — режим пересылки -- ✅ Добавлены поля: `message_search_mode`, `message_search_query`, `forwarding_message_id`, `is_selecting_forward_chat` - -#### Исправления: -- ✅ Переименованы тесты с динамическими датами (today/yesterday) на фиксированный old_date -- ✅ Удалены нестабильные snapshots зависящие от текущей даты -- ✅ Все модальные режимы теперь тестируются через snapshots - -#### Результаты: -- **8 новых snapshot тестов** — все проходят ✅ -- **8 snapshots приняты** через `cargo insta accept` -- **Все тесты проходят**: 71 тест (21 chat_list + 30 messages + 20 modals) - ---- - -### Фаза 1.2: Messages Snapshot Tests (95%) ✅ - -**Файл**: `tests/messages.rs` (19 тестов) - -#### Snapshot тесты для области сообщений: -- ✅ `snapshot_empty_chat` — пустой чат без сообщений -- ✅ `snapshot_single_incoming_message` — одно входящее сообщение -- ✅ `snapshot_single_outgoing_message` — одно исходящее сообщение -- ✅ `snapshot_date_separator_today` — разделитель "Сегодня" -- ✅ `snapshot_date_separator_yesterday` — разделитель "Вчера" -- ✅ `snapshot_sender_grouping` — группировка по отправителю (Alice → Alice → Bob) -- ✅ `snapshot_outgoing_sent` — исходящее с ✓ (отправлено) -- ✅ `snapshot_outgoing_read` — исходящее с ✓✓ (прочитано) -- ✅ `snapshot_edited_message` — сообщение с индикатором ✎ -- ✅ `snapshot_long_message_wrap` — длинное сообщение с переносом -- ✅ `snapshot_markdown_bold_italic_code` — **bold** *italic* `code` -- ✅ `snapshot_markdown_link_mention` — [links](url) и @mentions -- ✅ `snapshot_markdown_spoiler` — ||спойлер|| -- ✅ `snapshot_media_placeholder` — [Фото], [Видео] и т.д. -- ✅ `snapshot_reply_message` — reply с превью оригинала -- ✅ `snapshot_forwarded_message` — ↪ Переслано от Alice -- ✅ `snapshot_single_reaction` — сообщение с одной реакцией [👍] -- ✅ `snapshot_multiple_reactions` — [👍] 5 👎 3 -- ✅ `snapshot_selected_message` — выбранное сообщение (подсветка) - -#### Обновления TestAppBuilder: -- ✅ Добавлен метод `with_message(chat_id, message)` — добавить одно сообщение -- ✅ Добавлен метод `with_messages(chat_id, messages)` — добавить несколько сообщений -- ✅ Добавлен метод `selecting_message(index)` — установить выбранное сообщение -- ✅ Обновлен `build()` — применяет сообщения к `app.td_client.current_chat_messages` - -#### Результаты: -- **19 новых snapshot тестов** — все проходят ✅ -- **19 snapshots приняты** через `cargo insta accept` -- **Все тесты проходят**: 52 теста (21 chat_list + 31 messages) - ---- - -### Фаза 0: Инфраструктура (100%) - -#### 1. Зависимости -- ✅ Добавлено `insta = "1.34"` для snapshot тестов -- ✅ Добавлено `tokio-test = "0.4"` для async тестов -- ✅ Настроен `.gitignore` для `.snap.new` файлов - -#### 2. Test Helpers (5 модулей) - -**`tests/helpers/mod.rs`** -- Экспортирует все вспомогательные модули -- Удобный доступ к TestAppBuilder, FakeTdClient и утилитам - -**`tests/helpers/test_data.rs`** -- ✅ `TestChatBuilder` — fluent API для создания тестовых чатов -- ✅ `TestMessageBuilder` — fluent API для создания тестовых сообщений -- ✅ Хелперы: `create_test_chat()`, `create_test_message()`, `create_test_user()` -- ✅ Поддержка всех полей: unread, pinned, muted, mentions, reactions, reply, forward - -**`tests/helpers/fake_tdclient.rs`** -- ✅ `FakeTdClient` — in-memory мок для интеграционных тестов -- ✅ Методы: `send_message()`, `edit_message()`, `delete_message()`, `add_reaction()` -- ✅ Tracking отправленных/отредактированных/удалённых сообщений -- ✅ Fluent API для построения клиента с данными -- ✅ Встроенные юнит-тесты для проверки мока - -**`tests/helpers/snapshot_utils.rs`** -- ✅ `buffer_to_string()` — конвертация ratatui Buffer в строку для snapshots -- ✅ `render_to_buffer()` — рендеринг UI в виртуальный терминал -- ✅ `assert_ui_snapshot!` макрос для упрощения snapshot тестов -- ✅ Удаление trailing spaces для чистых snapshots -- ✅ Встроенные тесты - -**`tests/helpers/app_builder.rs`** -- ✅ `TestAppBuilder` — fluent API для создания тестового App -- ✅ Методы: `with_chat()`, `selected_chat()`, `message_input()`, `searching()`, etc. -- ✅ Поддержка всех режимов: edit, reply, search, reaction_picker, profile -- ✅ Встроенные тесты для билдера - -#### 3. Первые UI тесты - -**`tests/ui/chat_list_test.rs`** (9 тестов) -- ✅ snapshot_empty_chat_list -- ✅ snapshot_chat_list_with_three_chats -- ✅ snapshot_chat_with_unread_count -- ✅ snapshot_chat_with_pinned -- ✅ snapshot_chat_with_muted -- ✅ snapshot_chat_with_mentions -- ✅ snapshot_selected_chat -- ✅ snapshot_chat_long_title -- ✅ snapshot_chat_search_mode - ---- - -## 📊 Метрики - -**Создано файлов**: 18 -- 5 helpers -- 6 snapshot test files (chat_list, messages, modals, input_field, footer, screens) -- 10 integration test files (send_message, edit_message, delete_message, reply_forward, reactions, search, drafts, navigation, profile, network_typing) -- 1 mod.rs - -**Строк кода**: ~6500+ -- Helpers: ~1000 строк -- Snapshot тесты: ~1200 строк -- Integration тесты: ~4300 строк - -**Тестов написано**: -- Snapshot тесты: 55 -- Integration тесты: 73 -- Helper тесты: ~12 -- **Всего: 140+ тестов** - -**Покрытие**: -- Фаза 0: Инфраструктура ✅ (100%) -- Фаза 1: UI Snapshot Tests ✅ (100%) - - 1.1 Chat List: 9/9 ✅ - - 1.2 Messages: 18/18 ✅ - - 1.3 Modals: 8/8 ✅ - - 1.4 Input Field: 7/7 ✅ - - 1.5 Footer: 6/6 ✅ - - 1.6 Screens: 7/7 ✅ -- Фаза 2: Integration Tests ✅ (100%!) - - 2.1 Send Message: 6/6 ✅ - - 2.2 Edit Message: 6/6 ✅ - - 2.3 Delete Message: 6/6 ✅ - - 2.4 Reply & Forward: 8/8 ✅ - - 2.5 Reactions: 10/10 ✅ - - 2.6 Search: 8/8 ✅ - - 2.7 Drafts: 7/7 ✅ - - 2.8 Navigation: 7/7 ✅ - - 2.9 Profile: 6/6 ✅ - - 2.10 Network & Typing: 9/9 ✅ - - 2.11 Copy: 9/9 ✅ (вместо 3!) - - 2.12 Config: 11/11 ✅ (вместо 8!) -- **Общий прогресс: 148/151 (98%) — ПРЕВЗОШЛИ ПЛАН!** 🎉 - ---- - -## 🏗️ Структура - -``` -tests/ -├── helpers/ -│ ├── mod.rs ✅ Создан -│ ├── app_builder.rs ✅ Создан + 5 тестов -│ ├── fake_tdclient.rs ✅ Создан + 4 теста -│ ├── snapshot_utils.rs ✅ Создан + 2 теста -│ └── test_data.rs ✅ Создан -└── ui/ - ├── mod.rs ✅ Создан - └── chat_list_test.rs ✅ Создан (9 snapshot тестов) -``` - ---- - -## 🎯 Примеры использования - -### Создание тестового чата -```rust -let chat = TestChatBuilder::new("Mom", 123) - .unread_count(5) - .pinned() - .muted() - .draft("Hello...") - .build(); -``` - -### Создание тестового App -```rust -let app = TestAppBuilder::new() - .with_chat(chat) - .selected_chat(123) - .message_input("Hello!") - .build(); -``` - -### Snapshot тест -```rust -#[test] -fn snapshot_my_ui() { - let app = TestAppBuilder::new() - .with_chat(create_test_chat("Mom", 123)) - .build(); - - let buffer = render_to_buffer(80, 24, |f| { - render_chat_list(f, f.size(), &app); - }); - - assert_snapshot!("my_ui", buffer_to_string(&buffer)); -} -``` - -### Мок клиент для интеграционных тестов -```rust -let mut client = FakeTdClient::new() - .with_chat(create_test_chat("Mom", 123)); - -let msg_id = client.send_message(123, "Hello".to_string(), None); -assert_eq!(client.sent_messages().len(), 1); -``` - ---- - -## 🎉 ВСЕ ОСНОВНЫЕ ТЕСТЫ ЗАВЕРШЕНЫ! - -### Прогресс: 98% (148/151 тестов) — ПРЕВЗОШЛИ ПЛАН! 🚀 - -**Все основные тесты готовы:** -- ✅ Phase 0: Инфраструктура (100%) -- ✅ Phase 1: UI Snapshot Tests (100%) — 55 тестов -- ✅ Phase 2: Integration Tests (100%!) — 93 теста - -**Превзошли план на 9 тестов!** -- Copy Flow: 9 тестов (вместо 3) -- Config Flow: 11 тестов (вместо 8) - -### Опциональные тесты (можно сделать позже) - -#### Фаза 3: E2E Smoke Tests (4 теста) -**Файл**: `tests/e2e/smoke_test.rs` - -- [ ] Приложение запускается без краша -- [ ] Приложение рендерит loading screen -- [ ] Приложение корректно завершается по Ctrl+C -- [ ] Минимальный размер терминала не крашит приложение - -**Примечание**: E2E тесты требуют реального TDLib или сложного мока, поэтому опциональны. - -#### Фаза 4: Дополнительные тесты (8 тестов) - -**4.1 Utils Tests** (5 тестов) -- [ ] `format_timestamp_with_tz` с разными timezone -- [ ] `parse_timezone_offset` валидные значения -- [ ] `parse_timezone_offset` инвалидные значения (fallback) -- [ ] `format_date` для сегодня, вчера, старых дат -- [ ] `format_was_online` для разных временных промежутков - -**4.2 Performance Benchmarks** (3 теста) -- [ ] Benchmark рендеринга 100 сообщений -- [ ] Benchmark рендеринга списка 50 чатов -- [ ] Benchmark форматирования markdown текста - -### Итого - -**Завершено**: 148 тестов (98%) -**Опционально**: 12 тестов (2%) -**Всего**: 160 тестов потенциально - ---- - -## 💡 Технические заметки - -### Текущие ограничения -1. **TestAppBuilder создаёт реальный TdClient** — подходит только для UI/snapshot тестов -2. **Для интеграционных тестов** понадобится рефакторинг: либо trait для TdClient, либо dependency injection - -### Решения -- Snapshot тесты используют TestAppBuilder (UI рендеринг без вызова TdClient методов) -- Интеграционные тесты будут использовать FakeTdClient напрямую -- Возможно потребуется создать `IntegrationTestSession` для комплексных сценариев - ---- - -## ✨ Качество кода - -**Все helpers покрыты тестами**: -- `app_builder.rs`: 5 тестов -- `fake_tdclient.rs`: 4 теста -- `snapshot_utils.rs`: 2 теста - -**Документация**: -- Все публичные функции имеют doc-комментарии -- Примеры использования в комментариях -- README-секция в TESTING_ROADMAP.md - ---- - -## 🎓 Что изучили - -1. **Snapshot testing** с insta — мощный инструмент для TUI -2. **ratatui::backend::TestBackend** — виртуальный терминал для тестов -3. **Fluent builder pattern** — удобно для построения тестовых данных -4. **Test helpers organization** — разделение на модули для переиспользования - ---- - -## 📝 Обновлённые файлы - -- `Cargo.toml` — добавлены dev-dependencies -- `.gitignore` — добавлены правила для snapshots -- `TESTING_ROADMAP.md` — обновлён прогресс -- `README.md` — добавлена ссылка на TESTING_ROADMAP -- `REFACTORING_ROADMAP.md` — добавлено предусловие о тестах - ---- - -**Статус**: Готов к продолжению! 🚀 -**Следующий шаг**: Запустить тесты и убедиться что всё компилируется, затем продолжить с Фазы 1.2 diff --git a/TESTING_ROADMAP.md b/TESTING_ROADMAP.md deleted file mode 100644 index 9e62235..0000000 --- a/TESTING_ROADMAP.md +++ /dev/null @@ -1,620 +0,0 @@ -# Testing Roadmap - -План покрытия tele-tui тестами с фокусом на интеграционные и e2e тесты. - -## Стратегия тестирования - -### Подход: Комбо (Snapshot + Integration + E2E) - -1. **Snapshot Testing (70%)** — проверка UI рендеринга через insta -2. **Integration Testing (25%)** — проверка логики и flow через FakeTdClient -3. **E2E Smoke Testing (5%)** — базовая проверка что приложение запускается - -### Почему не юнит-тесты? - -- TUI сложно тестировать через юниты (моки, хрупкость) -- Интеграционные тесты дают больше уверенности -- Snapshots ловят UI регрессии лучше, чем assert координат - ---- - -## Фаза 0: Инфраструктура - -### Зависимости - -- [x] Добавить `insta = "1.34"` в dev-dependencies -- [x] Добавить `tokio-test = "0.4"` в dev-dependencies -- [x] Настроить `.gitignore` для snapshots (добавить `tests/snapshots/*.new`) - -### Helpers и Test Utilities - -- [x] Создать `tests/helpers/mod.rs` -- [x] Создать `tests/helpers/app_builder.rs` — builder для тестового App -- [x] Создать `tests/helpers/fake_tdclient.rs` — mock TDLib клиент -- [x] Создать `tests/helpers/snapshot_utils.rs` — утилиты для snapshot тестов -- [x] Создать `tests/helpers/test_data.rs` — фикстуры данных (чаты, сообщения) - -```rust -// tests/helpers/mod.rs -pub mod app_builder; -pub mod fake_tdclient; -pub mod snapshot_utils; -pub mod test_data; - -pub use app_builder::TestAppBuilder; -pub use fake_tdclient::FakeTdClient; -pub use snapshot_utils::{render_to_string, assert_ui_snapshot}; -pub use test_data::{create_test_chat, create_test_message}; -``` - -**Файлы для создания**: -``` -tests/ -├── helpers/ -│ ├── mod.rs -│ ├── app_builder.rs -│ ├── fake_tdclient.rs -│ ├── snapshot_utils.rs -│ └── test_data.rs -└── snapshots/ # Создаётся insta автоматически -``` - ---- - -## Фаза 1: Snapshot Tests для UI (Приоритет: ВЫСОКИЙ) - -### 1.1 Chat List — Список чатов - -**Файл**: `tests/ui/chat_list_test.rs` - -- [x] Пустой список чатов -- [x] Список с 3 чатами (без индикаторов) -- [x] Чат с непрочитанными сообщениями `(5)` -- [x] Чат с иконкой закреплённого 📌 -- [x] Чат с иконкой mute 🔇 -- [x] Чат с индикатором mention @ -- [ ] Чат с онлайн-статусом ● -- [x] Выбранный чат (с ▌) -- [x] Список чатов в режиме поиска -- [x] Длинное название чата (обрезка) - -**Пример теста**: -```rust -#[test] -fn snapshot_chat_list_with_unread() { - let app = TestAppBuilder::new() - .with_chat(create_test_chat("Mom", 123, unread: 5)) - .with_chat(create_test_chat("Boss", 456, unread: 0)) - .build(); - - assert_ui_snapshot!("chat_list_with_unread", app, |f, app| { - render_chat_list(f, f.size(), app); - }); -} -``` - ---- - -### 1.2 Messages — Область сообщений - -**Файл**: `tests/messages.rs` - -- [x] Пустой чат (нет сообщений) -- [x] Одно входящее сообщение -- [x] Одно исходящее сообщение -- [x] Группировка по дате (разделитель "Сегодня") -- [x] Группировка по дате (разделитель "Вчера") -- [x] Группировка по отправителю (заголовок с именем) -- [x] Исходящее сообщение с ✓ (отправлено) -- [x] Исходящее сообщение с ✓✓ (прочитано) -- [x] Сообщение с индикатором редактирования ✎ -- [x] Длинное сообщение (wrap на несколько строк) -- [x] Markdown: жирный, курсив, код -- [x] Markdown: ссылка, упоминание -- [x] Markdown: спойлер -- [x] Сообщение с медиа-заглушкой [Фото] -- [x] Reply сообщение с превью -- [x] Пересланное сообщение (↪ Переслано от) -- [x] Сообщение с одной реакцией [👍] -- [x] Сообщение с несколькими реакциями [👍] 5 👎 3 -- [x] Выбранное сообщение (подсветка) - ---- - -### 1.3 Modals — Модальные окна - -**Файл**: `tests/modals.rs` - -- [x] Delete confirmation модалка -- [x] Emoji picker (8x6 сетка) -- [x] Emoji picker с выбранной реакцией (курсор) -- [x] Profile модалка (личный чат) -- [x] Profile модалка (группа) -- [x] Pinned message вверху чата -- [x] Search в чате (с результатами) -- [x] Forward mode (список чатов для пересылки) - ---- - -### 1.4 Input Field — Поле ввода - -**Файл**: `tests/input_field.rs` - -- [x] Пустое поле ввода -- [x] Поле ввода с текстом и курсором █ -- [x] Поле ввода с длинным текстом (2 строки) -- [x] Поле ввода с длинным текстом (10 строк, максимум) -- [x] Режим редактирования (с превью) -- [x] Режим reply (с превью сообщения) -- [x] Режим поиска (с query) - ---- - -### 1.5 Footer — Нижняя панель ✅ - -**Файл**: `tests/footer.rs` - -- [x] Footer в списке чатов (команды навигации) -- [x] Footer в открытом чате (команды сообщений) -- [x] Footer с индикатором "⚠ Нет сети" -- [x] Footer с индикатором "⏳ Подключение к прокси..." -- [x] Footer с индикатором "⏳ Подключение..." -- [x] Footer в режиме поиска - ---- - -### 1.6 Screens — Полные экраны ✅ - -**Файл**: `tests/screens.rs` - -- [x] Loading screen (default) -- [x] Loading screen (со статусом) -- [x] Auth screen (ввод телефона) -- [x] Auth screen (ввод кода) -- [x] Auth screen (ввод пароля 2FA) -- [x] Main screen (пустой список чатов) -- [x] Минимальный размер терминала (предупреждение) - ---- - -## Фаза 2: Integration Tests для логики (Приоритет: ВЫСОКИЙ) - -### 2.1 Send Message Flow ✅ - -**Файл**: `tests/send_message.rs` (6 тестов) - -- [x] Отправка текстового сообщения -- [x] Отправка нескольких сообщений -- [x] Отправка с markdown форматированием -- [x] Отправка в разные чаты -- [x] Получение входящего сообщения -- [x] Отправка с reply - ---- - -### 2.2 Edit Message Flow ✅ - -**Файл**: `tests/edit_message.rs` (6 тестов) - -- [x] Редактирование текста сообщения -- [x] Установка edit_date после редактирования -- [x] Проверка can_be_edited перед редактированием -- [x] Редактирование только своих сообщений -- [x] Множественные редактирования -- [x] Редактирование с форматированием - ---- - -### 2.3 Delete Message Flow ✅ - -**Файл**: `tests/delete_message.rs` (6 тестов) - -- [x] Удаление сообщения из списка -- [x] Множественные удаления -- [x] Проверка can_be_deleted -- [x] Удаление только своих сообщений -- [x] Удаление из разных чатов -- [x] Delete with revoke - ---- - -### 2.4 Reply & Forward Flow ✅ - -**Файл**: `tests/reply_forward.rs` (8 тестов) - -- [x] Reply на сообщение с превью -- [x] Reply сохраняет связь с оригиналом -- [x] Forward сообщения -- [x] Forward с sender_name -- [x] Forward в разные чаты -- [x] Reply + Forward комбо -- [x] Reply на forwarded сообщение -- [x] Forward reply сообщения - ---- - -### 2.5 Reactions Flow ✅ - -**Файл**: `tests/reactions.rs` (10 тестов) - -- [x] Добавление реакции на сообщение -- [x] Удаление реакции (toggle) -- [x] Множественные реакции на одно сообщение -- [x] Реакции от разных пользователей -- [x] Подсчёт реакций -- [x] Chosen реакция (своя) -- [x] Реакции обновляются в реальном времени -- [x] Получение доступных реакций чата -- [x] Реакции на forwarded сообщения -- [x] Очистка всех реакций - ---- - -### 2.6 Search Flow ✅ - -**Файл**: `tests/search.rs` (8 тестов) - -- [x] Поиск по названию чата -- [x] Поиск по @username -- [x] Поиск по сообщениям в чате -- [x] Навигация по результатам поиска -- [x] Case-insensitive поиск -- [x] Поиск с пробелами -- [x] Поиск возвращает пустой список если нет совпадений -- [x] Очистка поиска - ---- - -### 2.7 Drafts Flow ✅ - -**Файл**: `tests/drafts.rs` (7 тестов) - -- [x] Сохранение черновика при переключении чатов -- [x] Восстановление черновика при возврате -- [x] Удаление черновика после отправки -- [x] Черновики для разных чатов независимы -- [x] Индикатор черновика в списке чатов -- [x] Пустой черновик не сохраняется -- [x] Черновик сохраняется при закрытии чата - ---- - -### 2.8 Navigation Flow ✅ - -**Файл**: `tests/navigation.rs` (7 тестов) - -- [x] Навигация по списку чатов (↑/↓) -- [x] Открытие чата (Enter) -- [x] Закрытие чата (Esc) -- [x] Скролл сообщений (↑/↓) -- [x] Переключение между папками (1-9) -- [x] Навигация с wrap (переход с конца на начало) -- [x] Навигация в пустом списке - ---- - -### 2.9 Profile Flow ✅ - -**Файл**: `tests/profile.rs` (6 тестов) - -- [x] Открытие профиля личного чата -- [x] Профиль показывает имя и username -- [x] Профиль показывает телефон -- [x] Открытие профиля группы -- [x] Профиль группы показывает участников -- [x] Закрытие профиля (Esc) - ---- - -### 2.10 Network & Typing Flow ✅ - -**Файл**: `tests/network_typing.rs` (9 тестов) - -- [x] Typing indicator при наборе текста -- [x] Отправка typing action -- [x] Получение typing статуса -- [x] Typing timeout -- [x] Network state: WaitingForNetwork -- [x] Network state: ConnectingToProxy -- [x] Network state: Connecting -- [x] Network state: Updating -- [x] Network state: Ready - ---- - -### 2.11 Copy Flow ✅ - -**Файл**: `tests/copy.rs` (9 тестов - ПРЕВЗОШЛИ ПЛАН!) - -- [x] Форматирование простого сообщения -- [x] Форматирование с forward контекстом -- [x] Форматирование с reply контекстом -- [x] Форматирование с forward + reply одновременно -- [x] Форматирование длинного сообщения -- [x] Форматирование с markdown entities -- [x] Clipboard initialization -- [x] Копирование в реальный clipboard (ручное) -- [x] Кроссплатформенность clipboard - ---- - -### 2.12 Config Flow ✅ - -**Файл**: `tests/config.rs` (11 тестов - ПРЕВЗОШЛИ ПЛАН!) - -- [x] Дефолтные значения конфигурации -- [x] Кастомные значения конфигурации -- [x] Парсинг валидных цветов -- [x] Парсинг light цветов -- [x] Парсинг невалидного цвета с fallback -- [x] Case-insensitive парсинг цветов -- [x] TOML сериализация и десериализация -- [x] Частичный TOML использует дефолты -- [x] Различные форматы timezone -- [x] Загрузка credentials из переменных окружения -- [x] Проверка формата ошибки когда credentials не найдены - ---- - -## Фаза 3: E2E Integration Tests (Приоритет: СРЕДНИЙ) ✅ - -### 3.1 Smoke Tests ✅ -**Файл**: `tests/e2e_smoke.rs` (4 теста) - -- [x] Приложение запускается без краша -- [x] Проверка минимального размера терминала -- [x] Базовые константы приложения -- [x] Graceful shutdown флаг - -### 3.2 User Journey Tests ✅ -**Файл**: `tests/e2e_user_journey.rs` (8 тестов) - -- [x] App Launch → Auth → Chat List -- [x] Open Chat → Load History → Send Message -- [x] Receive Incoming Message While Chat Open -- [x] Multi-step conversation flow -- [x] Switch between chats -- [x] Edit message in conversation flow -- [x] Reply to message in conversation -- [x] Network state changes during conversation - -**Итого**: 12/12 E2E тестов (100%) ✅ - -**Примечание**: Все тесты используют FakeTdClient для полной симуляции TDLib без реального подключения. - ---- - -## Фаза 4: Дополнительные тесты (Приоритет: НИЗКИЙ) - -### 4.1 Utils Tests - -**Файл**: `tests/unit/utils_test.rs` - -- [ ] `format_timestamp_with_tz` с разными timezone -- [ ] `parse_timezone_offset` валидные значения -- [ ] `parse_timezone_offset` инвалидные значения (fallback) -- [ ] `format_date` для сегодня, вчера, старых дат -- [ ] `format_was_online` для разных временных промежутков - -### 4.2 Performance Tests - -**Файл**: `tests/performance/render_bench.rs` - -- [ ] Benchmark рендеринга 100 сообщений -- [ ] Benchmark рендеринга списка 50 чатов -- [ ] Benchmark форматирования markdown текста - ---- - -## Метрики прогресса - -### Фаза 0: Инфраструктура -- [x] 8/8 задач выполнено ✅ - -### Фаза 1: Snapshot Tests ✅ -- [x] 1.1 Chat List: 10/10 (100%) ✅ -- [x] 1.2 Messages: 19/19 (100%) ✅ -- [x] 1.3 Modals: 8/8 (100%) ✅ -- [x] 1.4 Input Field: 7/7 (100%) ✅ -- [x] 1.5 Footer: 6/6 (100%) ✅ -- [x] 1.6 Screens: 7/7 (100%) ✅ -- **Итого: 57/57 snapshot тестов (100%)** ✅ - -### Фаза 2: Integration Tests ✅ -- [x] 2.1 Send Message: 6/6 ✅ -- [x] 2.2 Edit Message: 6/6 ✅ -- [x] 2.3 Delete Message: 6/6 ✅ -- [x] 2.4 Reply & Forward: 8/8 ✅ -- [x] 2.5 Reactions: 10/10 ✅ -- [x] 2.6 Search: 8/8 ✅ -- [x] 2.7 Drafts: 7/7 ✅ -- [x] 2.8 Navigation: 7/7 ✅ -- [x] 2.9 Profile: 6/6 ✅ -- [x] 2.10 Network & Typing: 9/9 ✅ -- [x] 2.11 Copy: 9/9 ✅ (вместо 3!) -- [x] 2.12 Config: 11/11 ✅ (вместо 8!) -- **Итого: 93/93 интеграционных тестов (100%!) — ПРЕВЗОШЛИ ПЛАН!** 🎉 - -### Фаза 3: E2E Integration -- [x] 3.1 Smoke Tests: 4/4 ✅ -- [x] 3.2 User Journey: 8/8 ✅ -- **Итого: 12/12 E2E тестов (100%)** ✅ - -### Фаза 4: Дополнительно -- [ ] 4.1 Utils: 0/5 -- [ ] 4.2 Performance: 0/3 -- **Итого: 0/8 дополнительных тестов** - ---- - -## Общий прогресс - -**Всего**: 164/171 тестов (96%) — ПРЕВЗОШЛИ ПЛАН! 🎉🎉🎉 - -**Фаза 0 (Инфраструктура)**: ✅ Завершена (100%) -**Фаза 1 (UI Snapshot Tests)**: ✅ 57/57 (100%) — ЗАВЕРШЕНА! 🎉 -- 1.1 Chat List: 10/10 (включая онлайн-статус) ✅ -- 1.2 Messages: 19/19 ✅ -- 1.3 Modals: 8/8 ✅ -- 1.4 Input Field: 7/7 ✅ -- 1.5 Footer: 6/6 ✅ -- 1.6 Screens: 7/7 ✅ - -**Фаза 2 (Integration Tests)**: ✅ 93/93 (100%!) — ПРЕВЗОШЛИ ПЛАН! -- Завершено: 2.1-2.12 ✅ -- Превзошли план на 9 тестов: Copy (9 вместо 3), Config (11 вместо 8) - -**Фаза 3 (E2E Integration Tests)**: ✅ 12/12 (100%) — ЗАВЕРШЕНА! 🎉 -- Smoke Tests: 4/4 ✅ -- User Journey: 8/8 ✅ - -**Опционально**: -- Фаза 4 (Utils + Performance): 0/8 - ---- - -## Приоритизация - -### Критичные (делать в первую очередь): -1. **Фаза 0**: Инфраструктура (без неё никуда) -2. **1.2**: Messages snapshots (ядро приложения) -3. **2.1**: Send message (основной flow) -4. **2.8**: Navigation (базовая навигация) - -### Важные (делать после критичных): -5. **1.1**: Chat list snapshots -6. **2.2**: Edit message -7. **2.3**: Delete message -8. **2.5**: Reactions -9. **2.6**: Search - -### Желательные (можно отложить): -10. **1.3-1.6**: Остальные snapshots -11. **2.4, 2.7, 2.9-2.12**: Остальные flows -12. **Фаза 3**: E2E smoke tests - -### Опциональные (по желанию): -13. **Фаза 4**: Utils и performance - ---- - -## Технологии - -### Основные -- **insta** — snapshot testing -- **tokio-test** — async testing utilities -- **ratatui::backend::TestBackend** — виртуальный терминал - -### Дополнительные (опционально) -- **expectrl** — для E2E тестов с реальным бинарником -- **criterion** — для бенчмарков (фаза 4.2) -- **mockall** — если понадобятся моки (скорее всего нет) - ---- - -## Примеры структуры тестов - -### Snapshot Test -```rust -use insta::assert_snapshot; -use ratatui::backend::TestBackend; -use ratatui::Terminal; - -#[test] -fn snapshot_messages_with_reactions() { - let mut terminal = Terminal::new(TestBackend::new(80, 24)).unwrap(); - - let app = TestAppBuilder::new() - .with_message(create_test_message("Hello!", reactions: vec![ - reaction("👍", 1, chosen: true), - reaction("👎", 3, chosen: false), - ])) - .build(); - - terminal.draw(|f| { - render_messages(f, f.size(), &app); - }).unwrap(); - - let buffer = terminal.backend().buffer(); - assert_snapshot!(buffer_to_string(buffer)); -} -``` - -### Integration Test -```rust -use crate::helpers::{TestAppBuilder, FakeTdClient}; - -#[tokio::test] -async fn test_send_message_updates_ui() { - let fake_client = FakeTdClient::new() - .with_chat("Mom", 123); - - let mut app = TestAppBuilder::new() - .with_client(fake_client) - .with_selected_chat(123) - .build(); - - // Ввод текста - app.input_text = "Hello!".to_string(); - - // Отправка - app.handle_key(KeyCode::Enter).await; - - // Проверки - assert_eq!(app.input_text, ""); // Инпут очистился - assert_eq!(app.current_messages().len(), 1); - assert_eq!(app.current_messages()[0].text, "Hello!"); - assert_eq!(fake_client.sent_messages().len(), 1); -} -``` - ---- - -## Команды - -```bash -# Прогнать все тесты -cargo test - -# Прогнать только snapshot тесты -cargo test --test ui - -# Прогнать только integration тесты -cargo test --test integration - -# Обновить snapshots (после ревью изменений) -cargo insta review - -# Принять все новые snapshots -cargo insta accept - -# Показать diff для изменённых snapshots -cargo insta test --review -``` - ---- - -## Правила - -1. **Один тест = один сценарий** — не делать мега-тесты -2. **Snapshots коммитим** — они часть тестов -3. **Фикстуры переиспользуем** — общие данные в `test_data.rs` -4. **Тесты изолированы** — каждый тест создаёт свой App -5. **Порядок не важен** — тесты можно запускать в любом порядке - ---- - -## TODO перед началом - -- [ ] Прочитать документацию insta: https://insta.rs/ -- [ ] Решить: нужен ли trait для TdClient или достаточно FakeTdClient -- [ ] Обсудить: какие тесты делать в первую очередь - ---- - -## Примечания - -- Этот документ будет обновляться по мере написания тестов -- После завершения фазы — отмечать в метриках -- Если тест падает или не актуален — документировать причину -- Snapshots хранятся в `tests/snapshots/__snapshots__/` diff --git a/config.example.toml b/config.example.toml deleted file mode 100644 index 806a875..0000000 --- a/config.example.toml +++ /dev/null @@ -1,35 +0,0 @@ -# Telegram TUI Configuration Example -# Copy this file to ~/.config/tele-tui/config.toml - -[general] -# Timezone offset (e.g., "+03:00", "-05:00") -timezone = "+03:00" - -[colors] -# Colors: red, green, blue, yellow, cyan, magenta, white, black, gray -# Also available: lightred, lightgreen, lightblue, lightyellow, lightcyan, lightmagenta -incoming_message = "white" -outgoing_message = "green" -selected_message = "yellow" -reaction_chosen = "yellow" -reaction_other = "gray" - -[notifications] -# Enable desktop notifications for new messages -enabled = true - -# Only notify when you are mentioned (@username) -only_mentions = false - -# Show message preview text in notifications -show_preview = true - -# Notification timeout in milliseconds (0 = system default) -timeout_ms = 5000 - -# Notification urgency level: "low", "normal", "critical" -# Note: Only works on Linux (libnotify), ignored on macOS/Windows -urgency = "normal" - -# Note: Notifications respect Telegram's mute settings -# Muted chats won't trigger notifications