Files
telegram-tui/TESTING_ROADMAP.md
Mikhail Kilin 68a2b7a982 fixes
2026-01-28 11:39:21 +03:00

570 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/ui/input_test.rs`
- [ ] Пустое поле ввода
- [ ] Поле ввода с текстом и курсором █
- [ ] Поле ввода с длинным текстом (2 строки)
- [ ] Поле ввода с длинным текстом (10 строк, максимум)
- [ ] Режим редактирования (с превью)
- [ ] Режим reply (с превью сообщения)
- [ ] Режим поиска (с query)
---
### 1.5 Footer — Нижняя панель
**Файл**: `tests/ui/footer_test.rs`
- [ ] Footer в списке чатов (команды навигации)
- [ ] Footer в открытом чате (команды сообщений)
- [ ] Footer с индикатором "⚠ Нет сети"
- [ ] Footer с индикатором "⏳ Подключение..."
- [ ] Footer в режиме поиска
- [ ] Footer в режиме выбора сообщения
---
### 1.6 Screens — Полные экраны
**Файл**: `tests/ui/screens_test.rs`
- [ ] Loading screen
- [ ] Auth screen (ввод телефона)
- [ ] Auth screen (ввод кода)
- [ ] Auth screen (ввод пароля 2FA)
- [ ] Main screen (папки + чаты + пустая область)
- [ ] Main screen (папки + чаты + открытый чат)
- [ ] Минимальный размер терминала (предупреждение)
---
## Фаза 2: Integration Tests для логики (Приоритет: ВЫСОКИЙ)
### 2.1 Send Message Flow
**Файл**: `tests/integration/send_message_test.rs`
- [ ] Отправка текстового сообщения
- [ ] Отправка сообщения обновляет UI
- [ ] Отправка пустого сообщения игнорируется
- [ ] Отправка с markdown форматированием
- [ ] Счётчик непрочитанных обнуляется при открытии чата
- [ ] Новое сообщение появляется в реальном времени
---
### 2.2 Edit Message Flow
**Файл**: `tests/integration/edit_message_test.rs`
- [ ] ↑ при пустом инпуте активирует режим выбора
- [ ] Enter в режиме выбора начинает редактирование
- [ ] Изменение текста и Enter сохраняет
- [ ] Esc отменяет редактирование
- [ ] Редактирование только своих сообщений
- [ ] Индикатор ✎ появляется после редактирования
---
### 2.3 Delete Message Flow
**Файл**: `tests/integration/delete_message_test.rs`
- [ ] d в режиме выбора открывает модалку
- [ ] y в модалке удаляет сообщение
- [ ] n в модалке отменяет удаление
- [ ] Esc отменяет удаление
- [ ] Сообщение исчезает из списка после удаления
- [ ] Удаление только своих сообщений
---
### 2.4 Reply & Forward Flow
**Файл**: `tests/integration/reply_forward_test.rs`
- [ ] r в режиме выбора активирует reply mode
- [ ] Превью сообщения отображается в инпуте
- [ ] Отправка reply создаёт связь с оригиналом
- [ ] Esc отменяет reply mode
- [ ] f в режиме выбора активирует forward mode
- [ ] Выбор чата стрелками в forward mode
- [ ] Enter пересылает сообщение
- [ ] Пересланное сообщение показывает "↪ Переслано от"
---
### 2.5 Reactions Flow
**Файл**: `tests/integration/reactions_test.rs`
- [ ] e открывает emoji picker
- [ ] Навигация стрелками по сетке эмодзи
- [ ] Enter добавляет реакцию
- [ ] Повторный Enter удаляет реакцию (toggle)
- [ ] Esc закрывает emoji picker
- [ ] Реакция появляется под сообщением
- [ ] Своя реакция в рамках [👍]
- [ ] Чужая реакция без рамок 👍
- [ ] Реакция 1 человека: только эмодзи
- [ ] Реакция 2+ людей: эмодзи + счётчик
---
### 2.6 Search Flow
**Файл**: `tests/integration/search_test.rs`
- [ ] Ctrl+S активирует поиск по чатам
- [ ] Фильтрация чатов по названию
- [ ] Фильтрация чатов по @username
- [ ] Esc закрывает поиск
- [ ] Ctrl+F активирует поиск в чате
- [ ] n переходит к следующему результату
- [ ] N переходит к предыдущему результату
- [ ] Подсветка найденных совпадений
---
### 2.7 Drafts Flow
**Файл**: `tests/integration/drafts_test.rs`
- [ ] Переключение между чатами сохраняет текст
- [ ] Возврат в чат восстанавливает текст
- [ ] Отправка сообщения удаляет черновик
- [ ] Индикатор черновика в списке чатов
---
### 2.8 Navigation Flow
**Файл**: `tests/integration/navigation_test.rs`
- [ ] ↑/↓ навигация по списку чатов
- [ ] Enter открывает чат
- [ ] Esc закрывает чат
- [ ] 1-9 переключение между папками
- [ ] ↑/↓ скролл сообщений в чате
- [ ] Подгрузка старых сообщений при скролле вверх
- [ ] Русская раскладка (р о л д)
---
### 2.9 Profile Flow
**Файл**: `tests/integration/profile_test.rs`
- [ ] i открывает профиль в личном чате
- [ ] Профиль показывает имя, username, телефон
- [ ] i открывает профиль в группе
- [ ] Профиль группы показывает название, описание, участников
- [ ] Esc закрывает профиль
---
### 2.10 Copy Flow
**Файл**: `tests/integration/copy_test.rs`
- [ ] y в режиме выбора копирует текст
- [ ] Clipboard содержит правильный текст
- [ ] Копирование работает на разных платформах
---
### 2.11 Typing Indicator Flow
**Файл**: `tests/integration/typing_test.rs`
- [ ] Ввод текста отправляет статус "печатает"
- [ ] Получение статуса показывает "печатает..." в UI
- [ ] Статус исчезает через timeout
---
### 2.12 Config Flow
**Файл**: `tests/integration/config_test.rs`
- [ ] Загрузка конфига из ~/.config/tele-tui/config.toml
- [ ] Создание дефолтного конфига если отсутствует
- [ ] Применение timezone к отображению времени
- [ ] Применение цветов к сообщениям
- [ ] Валидация невалидного timezone
- [ ] Валидация невалидного цвета
- [ ] Загрузка credentials: приоритет XDG → .env
- [ ] Ошибка если credentials не найдены
---
## Фаза 3: E2E Smoke Tests (Приоритет: СРЕДНИЙ)
**Файл**: `tests/e2e/smoke_test.rs`
- [ ] Приложение запускается без краша
- [ ] Приложение рендерит loading screen
- [ ] Приложение корректно завершается по Ctrl+C
- [ ] Минимальный размер терминала не крашит приложение
**Примечание**: E2E тесты опциональны, так как требуют реального 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: 9/10 (90%)
- [x] 1.2 Messages: 18/19 (95%) ✅
- [x] 1.3 Modals: 8/8 (100%) ✅
- [ ] 1.4 Input Field: 0/7
- [ ] 1.5 Footer: 0/6
- [ ] 1.6 Screens: 0/7
- **Итого: 35/57 snapshot тестов (61%)**
### Фаза 2: Integration Tests
- [ ] 2.1 Send Message: 0/6
- [ ] 2.2 Edit Message: 0/6
- [ ] 2.3 Delete Message: 0/6
- [ ] 2.4 Reply & Forward: 0/8
- [ ] 2.5 Reactions: 0/10
- [ ] 2.6 Search: 0/8
- [ ] 2.7 Drafts: 0/4
- [ ] 2.8 Navigation: 0/7
- [ ] 2.9 Profile: 0/5
- [ ] 2.10 Copy: 0/3
- [ ] 2.11 Typing: 0/3
- [ ] 2.12 Config: 0/8
- **Итого: 0/74 интеграционных тестов**
### Фаза 3: E2E Smoke
- [ ] 0/4 smoke тестов
### Фаза 4: Дополнительно
- [ ] 4.1 Utils: 0/5
- [ ] 4.2 Performance: 0/3
- **Итого: 0/8 дополнительных тестов**
---
## Общий прогресс
**Всего**: 35/151 тестов (23%)
**Фаза 0 (Инфраструктура)**: ✅ Завершена
**Фаза 1.1 (Chat List)**: 9/10 (90%)
**Фаза 1.2 (Messages)**: 18/19 (95%) ✅
**Фаза 1.3 (Modals)**: 8/8 (100%) ✅
---
## Приоритизация
### Критичные (делать в первую очередь):
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__/`