# Envision
[](https://github.com/ryanoneill/envision/actions/workflows/ci.yml)
[](https://codecov.io/gh/ryanoneill/envision)
[](https://docs.rs/envision)
[](https://crates.io/crates/envision)
[](LICENSE)
A [ratatui](https://github.com/ratatui/ratatui) framework for collaborative TUI development with headless testing support.
## Features
- **Headless Testing** - Render your TUI without a terminal using `CaptureBackend`
- **TEA Architecture** - The Elm Architecture pattern with `App`, `Runtime`, and `Command`
- **Async Runtime** - Full async support with subscriptions, timers, and async commands
- **Widget Annotations** - Attach semantic metadata to widgets for testing and accessibility
- **Test Harness** - Fluent assertions and snapshot testing for TUI applications
- **Input Simulation** - Programmatically simulate keyboard and mouse events
- **Multiple Output Formats** - Plain text, ANSI-colored, and JSON output
## Quick Start
Add envision to your project:
```bash
cargo add envision
```
### Basic Capture Backend
```rust
use envision::backend::CaptureBackend;
use ratatui::Terminal;
use ratatui::widgets::Paragraph;
// Create a headless terminal
let backend = CaptureBackend::new(80, 24);
let mut terminal = Terminal::new(backend)?;
// Render something
})?;
// Capture the output
println!("{}", terminal.backend());
```
### TEA Architecture
```rust
use envision::prelude::*;
struct MyApp;
#[derive(Default, Clone)]
struct State {
count: i32,
}
#[derive(Clone)]
enum Msg {
Increment,
Decrement,
}
impl App for MyApp {
type State = State;
type Message = Msg;
fn init() -> (State, Command<Msg>) {
(State::default(), Command::none())
}
fn update(state: &mut State, msg: Msg) -> Command<Msg> {
match msg {
Msg::Increment => state.count += 1,
Msg::Decrement => state.count -= 1,
}
Command::none()
}
fn view(state: &State, frame: &mut Frame) {
let text = format!("Count: {}", state.count);
frame.render_widget(Paragraph::new(text), frame.area());
}
}
```
### Testing with Runtime
```rust
use envision::prelude::*;
#[test]
fn test_my_app() {
// Use Runtime::headless for testing App implementations
let mut runtime = Runtime::<MyApp, _>::headless(80, 24).unwrap();
runtime.dispatch(Msg::Increment);
runtime.dispatch(Msg::Increment);
runtime.render().unwrap();
assert!(runtime.contains_text("Count: 2"));
}
```
### Test Harness for Custom Rendering
```rust
use envision::harness::TestHarness;
use ratatui::widgets::Paragraph;
let mut harness = TestHarness::new(80, 24);
});
harness.assert_contains("Hello!");
```
## Examples
Run the examples to see envision in action:
```bash
# Basic capture backend usage
cargo run --example capture_backend
# TEA architecture with sync runtime
cargo run --example counter_app
# Async runtime with subscriptions
cargo run --example async_counter
# Test harness and assertions
cargo run --example test_harness
# Widget annotations
cargo run --example annotations
```
## Architecture
Envision follows The Elm Architecture (TEA) pattern:
```
┌─────────────────────────────────────────────────────────┐
│ Application │
│ │
│ ┌─────────┐ ┌────────┐ ┌──────────────────┐ │
│ │ State │────▶│ View │────▶│ Terminal/Frame │ │
│ └─────────┘ └────────┘ └──────────────────┘ │
│ ▲ │
│ │ │
│ ┌─────────┐ ┌────────────────────┐ │
│ │ Update │◀────│ Message/Events │ │
│ └─────────┘ └────────────────────┘ │
│ │ ▲ │
│ ▼ │ │
│ ┌─────────┐ ┌────────────────────┐ │
│ │ Command │────▶│ Effect Handler │ │
│ └─────────┘ └────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
## Modules
| `backend` | `CaptureBackend` for headless rendering |
| `app` | TEA architecture: `App`, `Runtime`, `Command`, subscriptions |
| `harness` | `TestHarness` and `AsyncTestHarness` for testing |
| `annotation` | Widget annotations with `Annotate` and `AnnotationRegistry` |
| `input` | Input simulation with `EventQueue` |
| `adapter` | `DualBackend` for simultaneous real + capture rendering |
## Minimum Supported Rust Version
The minimum supported Rust version is **1.81**.
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.