Files
ui-agregator/docs/testing-possible-solutions.md
Alexander 5bee7092d3 feat: add insta snapshot testing for TUI components
- Add insta dev-dependency for visual regression testing
- Create lib.rs to expose modules for integration tests
- Add snapshot tests for progress_bar, topbar, library, help modal
- Add unit tests for LibraryState navigation
- Move all tests to tests/ directory (proper Rust convention)
- Make build.rs skip proto compilation when protoc unavailable
- Add docs/testing-possible-solutions.md with testing strategies
2026-05-09 11:35:10 +02:00

4.7 KiB

Testing Strategies for Ratatui TUI Applications

Research summary for testing the ui-agregator TUI application.

1. TestBackend + Buffer Assertions (Built-in)

Ratatui provides TestBackend - an in-memory terminal mock for testing without a real TTY.

use ratatui::{backend::TestBackend, Terminal};

#[test]
fn test_widget_renders() {
    let mut terminal = Terminal::new(TestBackend::new(80, 24)).unwrap();
    
    terminal.draw(|frame| {
        frame.render_widget(MyWidget::new(), frame.area());
    }).unwrap();
    
    terminal.backend().assert_buffer_lines([
        "Expected line 1",
        "Expected line 2",
    ]);
}

Best for: Unit testing widgets, layout logic, pure rendering functions.

Key Methods:

  • TestBackend::new(width, height) - Create in-memory terminal
  • backend.buffer() - Access rendered buffer
  • backend.assert_buffer(&expected) - Assert buffer equality
  • backend.assert_buffer_lines(lines) - Assert against string lines

2. Snapshot Testing with insta (Visual Regression)

Captures rendered output to .snap files; detects unexpected visual changes.

Official Recipe: https://ratatui.rs/recipes/testing/snapshots/

Setup

[dev-dependencies]
insta = "1.40.0"
cargo install cargo-insta

Usage Pattern

#[test]
fn test_library_view_snapshot() {
    let mut terminal = Terminal::new(TestBackend::new(80, 40)).unwrap();
    let mut state = LibraryState::new(vec![/* test data */]);
    
    terminal.draw(|f| render_library(f, f.area(), &mut state)).unwrap();
    
    insta::assert_snapshot!(terminal.backend());
}

Workflow

  1. First run: Creates .snap file in snapshots/ directory
  2. Subsequent runs: Compares against snapshot
  3. Review changes: cargo insta review - interactive diff viewer
  4. Update snapshots: cargo insta test --review

Best for: Catching visual regressions, complex layouts, multi-component views.

Note: Color/style information is not currently captured in snapshots.


3. Direct Unit Tests (State Logic, No Rendering)

Test state machines and business logic without rendering.

#[test]
fn test_library_navigation() {
    let mut state = LibraryState::new(test_artists());
    
    state.move_down();
    assert_eq!(state.selected_artist_index(), Some(1));
    
    state.focus_right();
    assert_eq!(state.focus, LibraryFocus::Albums);
}

Best for: State transitions, event handling logic, data transformations.


4. PTY-Based Integration Tests (ratatui-testlib)

Real terminal emulation with event simulation for end-to-end testing.

[dev-dependencies]
ratatui-testlib = "0.1"
#[test]
fn test_tab_switching_integration() -> Result<()> {
    let mut harness = TuiTestHarness::new(80, 24)?;
    harness.spawn(CommandBuilder::new("./ui-agregator"))?;
    
    harness.wait_for(|s| s.contents().contains("Library"))?;
    harness.send_key(KeyCode::Char('2'))?;
    harness.wait_for(|s| s.contents().contains("Wanted"))?;
    
    Ok(())
}

Best for: Full E2E scenarios, keyboard/mouse flow testing, CI validation.


5. Property-Based Testing (proptest)

Test invariants across random inputs - excellent for coordinate math and bounds checking.

[dev-dependencies]
proptest = "1.4"
proptest! {
    #[test]
    fn click_never_panics(x in 0u16..200, y in 0u16..100) {
        let mut app = App::new();
        app.size = Rect::new(0, 0, 80, 24);
        app.handle_click(x, y, MouseButton::Left);
    }
}

Best for: Click handling, scroll bounds, layout calculations.


Testing Plan for ui-agregator

Component Strategy Priority
LibraryState navigation Unit tests High
NotificationManager TTL/history Unit tests High
App.handle_click() routing Unit + proptest High
Tab views (wanted, queue, history) Snapshot tests (insta) Medium
progress_bar.rs Already has 3 tests Done
Modals (help, quit) Snapshot tests Medium
gRPC response → model conversion Unit tests Medium
Full app E2E flows ratatui-testlib Low

[dev-dependencies]
insta = "1.40.0"
rstest = "0.22"           # Parameterized tests
proptest = "1.4"          # Property-based testing
# ratatui-testlib = "0.1" # Optional: PTY integration tests

Resources