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
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
# 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/
|
||||
Reference in New Issue
Block a user