# 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__/`