5bee7092d3
- 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
187 lines
4.7 KiB
Markdown
187 lines
4.7 KiB
Markdown
# 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.
|
|
|
|
```rust
|
|
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
|
|
|
|
```toml
|
|
[dev-dependencies]
|
|
insta = "1.40.0"
|
|
```
|
|
|
|
```bash
|
|
cargo install cargo-insta
|
|
```
|
|
|
|
### Usage Pattern
|
|
|
|
```rust
|
|
#[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.
|
|
|
|
```rust
|
|
#[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.
|
|
|
|
```toml
|
|
[dev-dependencies]
|
|
ratatui-testlib = "0.1"
|
|
```
|
|
|
|
```rust
|
|
#[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.
|
|
|
|
```toml
|
|
[dev-dependencies]
|
|
proptest = "1.4"
|
|
```
|
|
|
|
```rust
|
|
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 |
|
|
|
|
---
|
|
|
|
## Recommended Dependencies
|
|
|
|
```toml
|
|
[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
|
|
|
|
- **Official Testing Docs**: https://ratatui.rs/recipes/testing/
|
|
- **Snapshot Testing Recipe**: https://ratatui.rs/recipes/testing/snapshots/
|
|
- **TestBackend API**: https://docs.rs/ratatui/latest/ratatui/backend/struct.TestBackend.html
|
|
- **insta Documentation**: https://insta.rs/docs/
|
|
- **ratatui-testlib**: https://docs.rs/ratatui-testlib/
|