Compare commits
6 Commits
de18d6978b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d3565c9ff9 | |||
|
|
90776448ce | ||
| 6344e0ff6a | |||
|
|
c89a5e13f8 | ||
|
|
07a41ff796 | ||
|
|
e2971e5ff5 |
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -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]
|
||||
|
||||
## Логи
|
||||
Если есть логи или сообщения об ошибках, вставьте их сюда:
|
||||
```
|
||||
вставьте логи здесь
|
||||
```
|
||||
|
||||
## Дополнительный контекст
|
||||
Любая другая информация, которая может помочь в решении проблемы.
|
||||
34
.github/ISSUE_TEMPLATE/feature_request.md
vendored
34
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -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) и этой функции там нет
|
||||
|
||||
## Дополнительный контекст
|
||||
Скриншоты, ссылки на похожие реализации в других приложениях, и т.д.
|
||||
51
.github/pull_request_template.md
vendored
51
.github/pull_request_template.md
vendored
@@ -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 изменений.
|
||||
|
||||
## Дополнительные заметки
|
||||
|
||||
Любая дополнительная информация для ревьюверов.
|
||||
@@ -108,3 +108,14 @@ default_modes:
|
||||
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
|
||||
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
|
||||
fixed_tools: []
|
||||
|
||||
# override of the corresponding setting in serena_config.yml, see the documentation there.
|
||||
# If null or missing, the value from the global config is used.
|
||||
symbol_info_budget:
|
||||
|
||||
# The language backend to use for this project.
|
||||
# If not set, the global setting from serena_config.yml is used.
|
||||
# Valid values: LSP, JetBrains
|
||||
# Note: the backend is fixed at startup. If a project with a different backend
|
||||
# is activated post-init, an error will be returned.
|
||||
language_backend:
|
||||
|
||||
66
CHANGELOG.md
66
CHANGELOG.md
@@ -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
|
||||
125
CONTRIBUTING.md
125
CONTRIBUTING.md
@@ -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/)
|
||||
- Добавляйте комментарии для сложной логики
|
||||
|
||||
### Структура коммитов
|
||||
|
||||
```
|
||||
<type>: <краткое описание>
|
||||
|
||||
<подробное описание (опционально)>
|
||||
```
|
||||
|
||||
Типы:
|
||||
- `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).
|
||||
227
FAQ.md
227
FAQ.md
@@ -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.
|
||||
122
INSTALL.md
122
INSTALL.md
@@ -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/
|
||||
```
|
||||
@@ -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<ChatInfo>,
|
||||
pub selected_chat: Option<ChatId>,
|
||||
pub messages: HashMap<ChatId, Vec<MessageInfo>>,
|
||||
// ... еще 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<bool>;
|
||||
}
|
||||
```
|
||||
- [ ] Реализовать для каждого экрана:
|
||||
- `ChatListKeyHandler`
|
||||
- `MessagesKeyHandler`
|
||||
- `ComposeKeyHandler`
|
||||
- `SearchKeyHandler`
|
||||
|
||||
#### 6.2. Создать network utilities
|
||||
|
||||
- [ ] Создать `src/utils/network.rs`
|
||||
```rust
|
||||
async fn with_timeout<F, T>(f: F, timeout_ms: u64) -> Result<T>
|
||||
async fn with_retry<F, T>(f: F, max_retries: u32) -> Result<T>
|
||||
```
|
||||
|
||||
#### 6.3. Создать систему горячих клавиш ✅ ЗАВЕРШЕНО! (2026-02-04)
|
||||
|
||||
- [x] Создать `src/config/keybindings.rs` - **Выполнено**
|
||||
- Enum `Command` с 40+ командами (навигация, чат, сообщения, input)
|
||||
- Struct `KeyBinding` с поддержкой модификаторов (Ctrl, Shift, Alt и т.д.)
|
||||
- Struct `Keybindings` с HashMap<Command, Vec<KeyBinding>>
|
||||
- Поддержка множественных 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<T, String>
|
||||
|
||||
// В других
|
||||
Result<T, Box<dyn Error>>
|
||||
|
||||
// В третьих
|
||||
Result<T> // с неявным типом ошибки
|
||||
```
|
||||
|
||||
#### 7.2. Разные паттерны state management
|
||||
|
||||
- В одних местах флаги (`is_editing: bool`)
|
||||
- В других энумы (`EditMode::Active`)
|
||||
- В третьих Option (`editing_message: Option<MessageId>`)
|
||||
|
||||
#### 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 строк (в идеале)
|
||||
- Улучшенная тестируемость
|
||||
- Более четкое разделение ответственностей
|
||||
File diff suppressed because it is too large
Load Diff
64
SECURITY.md
64
SECURITY.md
@@ -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 недели
|
||||
- **Низкие**: включаются в следующий релиз
|
||||
|
||||
## Спасибо
|
||||
|
||||
Мы ценим ваш вклад в безопасность проекта!
|
||||
@@ -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
|
||||
@@ -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__/`
|
||||
@@ -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
|
||||
@@ -87,6 +87,7 @@ impl<T: TdClientTrait> NavigationMethods<T> for App<T> {
|
||||
#[cfg(feature = "images")]
|
||||
{
|
||||
self.photo_download_rx = None;
|
||||
self.pending_image_open = None;
|
||||
}
|
||||
// Сбрасываем состояние чата в нормальный режим
|
||||
self.chat_state = ChatState::Normal;
|
||||
|
||||
@@ -20,6 +20,19 @@ use crate::types::ChatId;
|
||||
use ratatui::widgets::ListState;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Pending intent to open the image modal once a photo finishes downloading.
|
||||
///
|
||||
/// Set when the user presses `v` on a photo that is still downloading.
|
||||
/// The main loop opens the modal automatically when the download completes.
|
||||
#[cfg(feature = "images")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PendingImageOpen {
|
||||
pub file_id: i32,
|
||||
pub message_id: crate::types::MessageId,
|
||||
pub photo_width: i32,
|
||||
pub photo_height: i32,
|
||||
}
|
||||
|
||||
/// State of the account switcher modal overlay.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AccountSwitcherState {
|
||||
@@ -123,6 +136,9 @@ pub struct App<T: TdClientTrait = TdClient> {
|
||||
/// Время последнего рендеринга изображений (для throttling до 15 FPS)
|
||||
#[cfg(feature = "images")]
|
||||
pub last_image_render_time: Option<std::time::Instant>,
|
||||
/// Pending intent: открыть модалку для этого фото когда загрузится
|
||||
#[cfg(feature = "images")]
|
||||
pub pending_image_open: Option<PendingImageOpen>,
|
||||
// Account lock
|
||||
/// Advisory file lock to prevent concurrent access to the same account
|
||||
pub account_lock: Option<std::fs::File>,
|
||||
@@ -219,6 +235,8 @@ impl<T: TdClientTrait> App<T> {
|
||||
image_modal: None,
|
||||
#[cfg(feature = "images")]
|
||||
last_image_render_time: None,
|
||||
#[cfg(feature = "images")]
|
||||
pending_image_open: None,
|
||||
// Voice playback
|
||||
audio_player: crate::audio::AudioPlayer::new().ok(),
|
||||
voice_cache: crate::audio::VoiceCache::new(audio_cache_size_mb).ok(),
|
||||
|
||||
@@ -584,45 +584,44 @@ async fn handle_view_image<T: TdClientTrait>(app: &mut App<T>) {
|
||||
});
|
||||
app.needs_redraw = true;
|
||||
}
|
||||
PhotoDownloadState::Downloading => {
|
||||
app.status_message = Some("Загрузка фото...".to_string());
|
||||
}
|
||||
PhotoDownloadState::NotDownloaded => {
|
||||
// Скачиваем фото и открываем
|
||||
app.status_message = Some("Загрузка фото...".to_string());
|
||||
app.needs_redraw = true;
|
||||
match app.td_client.download_file(file_id).await {
|
||||
Ok(path) => {
|
||||
// Обновляем состояние загрузки в сообщении
|
||||
for msg in app.td_client.current_chat_messages_mut() {
|
||||
if let Some(photo) = msg.photo_info_mut() {
|
||||
if photo.file_id == file_id {
|
||||
photo.download_state = PhotoDownloadState::Downloaded(path.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Открываем модалку
|
||||
app.image_modal = Some(ImageModalState {
|
||||
PhotoDownloadState::NotDownloaded | PhotoDownloadState::Downloading => {
|
||||
// Запоминаем намерение открыть модалку — откроется когда загрузится
|
||||
app.pending_image_open = Some(crate::app::PendingImageOpen {
|
||||
file_id,
|
||||
message_id: msg_id,
|
||||
photo_path: path,
|
||||
photo_width,
|
||||
photo_height,
|
||||
});
|
||||
app.status_message = None;
|
||||
app.status_message = Some("Загрузка фото...".to_string());
|
||||
app.needs_redraw = true;
|
||||
|
||||
// Если нет активной фоновой загрузки — запускаем свою
|
||||
if app.photo_download_rx.is_none() {
|
||||
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
app.photo_download_rx = Some(rx);
|
||||
let client_id = app.td_client.client_id();
|
||||
tokio::spawn(async move {
|
||||
let result = tokio::time::timeout(Duration::from_secs(30), async {
|
||||
match tdlib_rs::functions::download_file(
|
||||
file_id, 1, 0, 0, true, client_id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(tdlib_rs::enums::File::File(f))
|
||||
if f.local.is_downloading_completed
|
||||
&& !f.local.path.is_empty() =>
|
||||
{
|
||||
Ok(f.local.path)
|
||||
}
|
||||
Err(e) => {
|
||||
for msg in app.td_client.current_chat_messages_mut() {
|
||||
if let Some(photo) = msg.photo_info_mut() {
|
||||
if photo.file_id == file_id {
|
||||
photo.download_state = PhotoDownloadState::Error(e.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
app.error_message = Some(format!("Ошибка загрузки фото: {}", e));
|
||||
app.status_message = None;
|
||||
Ok(_) => Err("Файл не скачан".to_string()),
|
||||
Err(e) => Err(format!("{:?}", e)),
|
||||
}
|
||||
})
|
||||
.await;
|
||||
let result =
|
||||
result.unwrap_or_else(|_| Err("Таймаут загрузки".to_string()));
|
||||
let _ = tx.send((file_id, result));
|
||||
});
|
||||
}
|
||||
}
|
||||
PhotoDownloadState::Error(_) => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
41
src/main.rs
41
src/main.rs
@@ -216,6 +216,47 @@ async fn run_app<B: ratatui::backend::Backend, T: tdlib::TdClientTrait>(
|
||||
}
|
||||
}
|
||||
}
|
||||
// Если это фото ждёт открытия в модалке — открываем
|
||||
let pending_matches = app
|
||||
.pending_image_open
|
||||
.as_ref()
|
||||
.map(|p| p.file_id == file_id)
|
||||
.unwrap_or(false);
|
||||
if pending_matches {
|
||||
// Ищем путь из обновлённого состояния
|
||||
let downloaded_path = app
|
||||
.td_client
|
||||
.current_chat_messages()
|
||||
.iter()
|
||||
.find_map(|m| {
|
||||
m.photo_info().and_then(|p| {
|
||||
if p.file_id == file_id {
|
||||
if let PhotoDownloadState::Downloaded(ref path) =
|
||||
p.download_state
|
||||
{
|
||||
Some(path.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
});
|
||||
if let (Some(path), Some(pending)) =
|
||||
(downloaded_path, app.pending_image_open.take())
|
||||
{
|
||||
use crate::tdlib::ImageModalState;
|
||||
app.image_modal = Some(ImageModalState {
|
||||
message_id: pending.message_id,
|
||||
photo_path: path,
|
||||
photo_width: pending.photo_width,
|
||||
photo_height: pending.photo_height,
|
||||
});
|
||||
app.status_message = None;
|
||||
got_photos = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if got_photos {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user