Files
telegram-tui/TESTING_ROADMAP.md
Mikhail Kilin 126c7482af
Some checks failed
CI / Check (pull_request) Has been cancelled
CI / Format (pull_request) Has been cancelled
CI / Clippy (pull_request) Has been cancelled
CI / Build (macos-latest) (pull_request) Has been cancelled
CI / Build (ubuntu-latest) (pull_request) Has been cancelled
CI / Build (windows-latest) (pull_request) Has been cancelled
fixes
2026-01-29 01:22:57 +03:00

20 KiB
Raw Blame History

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: Инфраструктура

Зависимости

  • Добавить insta = "1.34" в dev-dependencies
  • Добавить tokio-test = "0.4" в dev-dependencies
  • Настроить .gitignore для snapshots (добавить tests/snapshots/*.new)

Helpers и Test Utilities

  • Создать tests/helpers/mod.rs
  • Создать tests/helpers/app_builder.rs — builder для тестового App
  • Создать tests/helpers/fake_tdclient.rs — mock TDLib клиент
  • Создать tests/helpers/snapshot_utils.rs — утилиты для snapshot тестов
  • Создать tests/helpers/test_data.rs — фикстуры данных (чаты, сообщения)
// 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

  • Пустой список чатов
  • Список с 3 чатами (без индикаторов)
  • Чат с непрочитанными сообщениями (5)
  • Чат с иконкой закреплённого 📌
  • Чат с иконкой mute 🔇
  • Чат с индикатором mention @
  • Чат с онлайн-статусом ●
  • Выбранный чат (с ▌)
  • Список чатов в режиме поиска
  • Длинное название чата (обрезка)

Пример теста:

#[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

  • Пустой чат (нет сообщений)
  • Одно входящее сообщение
  • Одно исходящее сообщение
  • Группировка по дате (разделитель "Сегодня")
  • Группировка по дате (разделитель "Вчера")
  • Группировка по отправителю (заголовок с именем)
  • Исходящее сообщение с ✓ (отправлено)
  • Исходящее сообщение с ✓✓ (прочитано)
  • Сообщение с индикатором редактирования ✎
  • Длинное сообщение (wrap на несколько строк)
  • Markdown: жирный, курсив, код
  • Markdown: ссылка, упоминание
  • Markdown: спойлер
  • Сообщение с медиа-заглушкой [Фото]
  • Reply сообщение с превью
  • Пересланное сообщение (↪ Переслано от)
  • Сообщение с одной реакцией [👍]
  • Сообщение с несколькими реакциями [👍] 5 👎 3
  • Выбранное сообщение (подсветка)

1.3 Modals — Модальные окна

Файл: tests/modals.rs

  • Delete confirmation модалка
  • Emoji picker (8x6 сетка)
  • Emoji picker с выбранной реакцией (курсор)
  • Profile модалка (личный чат)
  • Profile модалка (группа)
  • Pinned message вверху чата
  • Search в чате (с результатами)
  • Forward mode (список чатов для пересылки)

1.4 Input Field — Поле ввода

Файл: tests/input_field.rs

  • Пустое поле ввода
  • Поле ввода с текстом и курсором █
  • Поле ввода с длинным текстом (2 строки)
  • Поле ввода с длинным текстом (10 строк, максимум)
  • Режим редактирования (с превью)
  • Режим reply (с превью сообщения)
  • Режим поиска (с query)

Файл: tests/footer.rs

  • Footer в списке чатов (команды навигации)
  • Footer в открытом чате (команды сообщений)
  • Footer с индикатором "⚠ Нет сети"
  • Footer с индикатором " Подключение к прокси..."
  • Footer с индикатором " Подключение..."
  • Footer в режиме поиска

1.6 Screens — Полные экраны

Файл: tests/screens.rs

  • Loading screen (default)
  • Loading screen (со статусом)
  • Auth screen (ввод телефона)
  • Auth screen (ввод кода)
  • Auth screen (ввод пароля 2FA)
  • 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: Инфраструктура

  • 8/8 задач выполнено

Фаза 1: Snapshot Tests

  • 1.1 Chat List: 9/10 (90%)
  • 1.2 Messages: 18/19 (95%)
  • 1.3 Modals: 8/8 (100%)
  • 1.4 Input Field: 7/7 (100%)
  • 1.5 Footer: 0/6
  • 1.6 Screens: 0/7
  • Итого: 42/57 snapshot тестов (74%)

Фаза 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 дополнительных тестов

Общий прогресс

Всего: 42/151 тестов (28%)

Фаза 0 (Инфраструктура): Завершена Фаза 1.1 (Chat List): 9/10 (90%) Фаза 1.2 (Messages): 18/19 (95%) Фаза 1.3 (Modals): 8/8 (100%) Фаза 1.4 (Input Field): 7/7 (100%)


Приоритизация

Критичные (делать в первую очередь):

  1. Фаза 0: Инфраструктура (без неё никуда)
  2. 1.2: Messages snapshots (ядро приложения)
  3. 2.1: Send message (основной flow)
  4. 2.8: Navigation (базовая навигация)

Важные (делать после критичных):

  1. 1.1: Chat list snapshots
  2. 2.2: Edit message
  3. 2.3: Delete message
  4. 2.5: Reactions
  5. 2.6: Search

Желательные (можно отложить):

  1. 1.3-1.6: Остальные snapshots
  2. 2.4, 2.7, 2.9-2.12: Остальные flows
  3. Фаза 3: E2E smoke tests

Опциональные (по желанию):

  1. Фаза 4: Utils и performance

Технологии

Основные

  • insta — snapshot testing
  • tokio-test — async testing utilities
  • ratatui::backend::TestBackend — виртуальный терминал

Дополнительные (опционально)

  • expectrl — для E2E тестов с реальным бинарником
  • criterion — для бенчмарков (фаза 4.2)
  • mockall — если понадобятся моки (скорее всего нет)

Примеры структуры тестов

Snapshot Test

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

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);
}

Команды

# Прогнать все тесты
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__/