use std::ops::Not;
use envision::harness::{Assertion, TestHarness};
use envision::prelude::*;
use ratatui::layout::{Alignment, Constraint, Layout};
use ratatui::style::{Color, Modifier, Style};
use ratatui::widgets::{Block, Borders, Paragraph};
struct TodoApp;
#[derive(Default, Clone)]
struct TodoState {
items: Vec<String>,
selected: Option<usize>,
}
#[derive(Clone, Debug)]
#[allow(dead_code)]
enum TodoMsg {
Add(String),
Remove(usize),
Select(usize),
Clear,
}
impl App for TodoApp {
type State = TodoState;
type Message = TodoMsg;
fn init() -> (TodoState, Command<TodoMsg>) {
let state = TodoState {
items: vec!["Buy groceries".to_string(), "Walk the dog".to_string()],
selected: None,
};
(state, Command::none())
}
fn update(state: &mut TodoState, msg: TodoMsg) -> Command<TodoMsg> {
match msg {
TodoMsg::Add(item) => {
state.items.push(item);
}
TodoMsg::Remove(idx) => {
if idx < state.items.len() {
state.items.remove(idx);
state.selected = None;
}
}
TodoMsg::Select(idx) => {
if idx < state.items.len() {
state.selected = Some(idx);
}
}
TodoMsg::Clear => {
state.items.clear();
state.selected = None;
}
}
Command::none()
}
fn view(state: &TodoState, frame: &mut Frame) {
let area = frame.area();
let chunks = Layout::vertical([Constraint::Length(3), Constraint::Min(0)]).split(area);
let header = Paragraph::new(format!("Todo List ({} items)", state.items.len()))
.style(
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
)
.alignment(Alignment::Center)
.block(Block::default().borders(Borders::ALL).title("Header"));
frame.render_widget(header, chunks[0]);
let items_text: Vec<String> = state
.items
.iter()
.enumerate()
.map(|(i, item)| {
let marker = if state.selected == Some(i) { ">" } else { " " };
format!("{} {}. {}", marker, i + 1, item)
})
.collect();
let items_para = Paragraph::new(items_text.join("\n"))
.block(Block::default().borders(Borders::ALL).title("Items"));
frame.render_widget(items_para, chunks[1]);
}
}
fn demo_basic_harness() {
println!("=== Demo 1: Basic TestHarness ===\n");
let mut harness = TestHarness::new(60, 10);
harness
.render(|frame| {
let para = Paragraph::new("Hello, Test World!")
.style(Style::default().fg(Color::Green))
.block(Block::default().borders(Borders::ALL).title("Greeting"));
frame.render_widget(para, frame.area());
})
.unwrap();
println!("Rendered content:");
println!("{}", harness.screen());
harness.assert_contains("Hello, Test World!");
harness.assert_contains("Greeting");
println!("Assertions passed!\n");
}
fn demo_runtime_testing() {
println!("=== Demo 2: Virtual Terminal for App Testing ===\n");
let mut vt = Runtime::<TodoApp, _>::virtual_builder(60, 12)
.build()
.unwrap();
vt.tick().unwrap();
println!("Initial state:");
println!("{}", vt.display());
assert_eq!(vt.state().items.len(), 2);
assert!(vt.contains_text("2 items"));
assert!(vt.contains_text("Buy groceries"));
assert!(vt.contains_text("Walk the dog"));
vt.dispatch(TodoMsg::Add("Learn Rust".to_string()));
vt.tick().unwrap();
println!("After adding item:");
println!("{}", vt.display());
assert_eq!(vt.state().items.len(), 3);
assert!(vt.contains_text("3 items"));
assert!(vt.contains_text("Learn Rust"));
vt.dispatch(TodoMsg::Select(1));
vt.tick().unwrap();
println!("After selecting item 1:");
println!("{}", vt.display());
assert_eq!(vt.state().selected, Some(1));
assert!(vt.contains_text("> 2. Walk the dog"));
vt.dispatch(TodoMsg::Remove(1));
vt.tick().unwrap();
println!("After removing item:");
println!("{}", vt.display());
assert_eq!(vt.state().items.len(), 2);
assert!(!vt.contains_text("Walk the dog"));
println!("All virtual terminal tests passed!\n");
}
fn demo_assertions() {
println!("=== Demo 3: Declarative Assertions ===\n");
let mut harness = TestHarness::new(60, 10);
harness
.render(|frame| {
let text = "Error: File not found\nWarning: Low memory\nInfo: Operation complete";
let para =
Paragraph::new(text).block(Block::default().borders(Borders::ALL).title("Logs"));
frame.render_widget(para, frame.area());
})
.unwrap();
println!("Rendered logs:");
println!("{}", harness.screen());
let assertion = Assertion::all(vec![
Assertion::contains("Error:"),
Assertion::contains("Warning:"),
Assertion::contains("Info:"),
]);
match assertion.check(&harness) {
Ok(()) => println!("All log levels present!"),
Err(e) => println!("Assertion failed: {}", e),
}
let not_critical = Assertion::contains("CRITICAL").not();
match not_critical.check(&harness) {
Ok(()) => println!("No CRITICAL errors found!"),
Err(e) => println!("Unexpected: {}", e),
}
let has_status = Assertion::any(vec![
Assertion::contains("complete"),
Assertion::contains("success"),
Assertion::contains("done"),
]);
match has_status.check(&harness) {
Ok(()) => println!("Found a completion status!"),
Err(e) => println!("No status found: {}", e),
}
println!("\nAll assertion demos complete!\n");
}
fn demo_content_queries() {
println!("=== Demo 4: Content Queries ===\n");
let mut harness = TestHarness::new(40, 8);
harness
.render(|frame| {
let text = "Name: Alice\nAge: 30\nCity: New York";
let para =
Paragraph::new(text).block(Block::default().borders(Borders::ALL).title("Profile"));
frame.render_widget(para, frame.area());
})
.unwrap();
println!("Rendered profile:");
println!("{}", harness.screen());
println!("Contains 'Alice': {}", harness.contains("Alice"));
println!("Contains 'Bob': {}", harness.contains("Bob"));
let positions = harness.find_all_text(":");
println!("Found ':' at {} positions", positions.len());
println!("\nRow contents:");
for y in 0..harness.height() {
let row = harness.row(y);
if !row.trim().is_empty() {
println!(" Row {}: {}", y, row.trim());
}
}
println!("\nContent query demo complete!\n");
}
fn main() {
println!("╔══════════════════════════════════════╗");
println!("║ Test Harness Demo ║");
println!("╚══════════════════════════════════════╝\n");
demo_basic_harness();
demo_runtime_testing();
demo_assertions();
demo_content_queries();
println!("All demos complete!");
}