#![cfg(feature = "terminal")]
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use ratatui_toolkit::termtui::TermTui;
use std::time::Duration;
fn get_screen_text(term: &TermTui) -> Vec<String> {
let parser = term.parser.lock().unwrap();
let screen = parser.screen();
let grid = screen.primary_grid();
let size = grid.size();
let mut lines = Vec::new();
for row_idx in 0..size.rows {
if let Some(row) = grid.visible_row(row_idx) {
let line_text: String = (0..size.cols)
.filter_map(|col| {
row.get(col).and_then(|cell| {
let text = cell.text();
if text.is_empty() || cell.is_wide_continuation() {
None
} else {
Some(text)
}
})
})
.collect();
if !line_text.trim().is_empty() {
lines.push(line_text.trim().to_string());
}
}
}
lines
}
fn wait_for_output(term: &TermTui, expected: &str, timeout_ms: u64) -> bool {
let start = std::time::Instant::now();
while start.elapsed() < Duration::from_millis(timeout_ms) {
let lines = get_screen_text(term);
for line in &lines {
if line.contains(expected) {
return true;
}
}
tokio::time::sleep(Duration::from_millis($1)).await;
}
false
}
#[tokio::test]
async fn test_terminal_spawn_and_type() {
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
let mut term = TermTui::spawn_with_command("Test Terminal", &shell, &[])
.expect("Failed to spawn terminal");
let prompt_found = wait_for_output(&term, "$", 2000);
assert!(prompt_found, "Shell prompt should appear");
let command = "echo";
for ch in command.chars() {
let key = KeyEvent::new(KeyCode::Char(ch), KeyModifiers::NONE);
term.handle_key(key);
tokio::time::sleep(Duration::from_millis($1)).await;
}
let enter = KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE);
term.handle_key(enter);
tokio::time::sleep(Duration::from_millis($1)).await;
}
#[tokio::test]
async fn test_echo_command() {
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
let mut term =
TermTui::spawn_with_command("Echo Test", &shell, &[]).expect("Failed to spawn terminal");
tokio::time::sleep(Duration::from_millis($1)).await;
let command = "echo hello world";
for ch in command.chars() {
let key = KeyEvent::new(KeyCode::Char(ch), KeyModifiers::NONE);
term.handle_key(key);
tokio::time::sleep(Duration::from_millis($1)).await;
}
term.handle_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
let found = wait_for_output(&term, "hello world", 1000);
assert!(found, "Expected 'hello world' in terminal output");
let lines = get_screen_text(&term);
assert!(
lines.iter().any(|l| l.contains("hello world")),
"Output should contain 'hello world'"
);
}
#[tokio::test]
async fn test_pwd_command() {
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
let mut term =
TermTui::spawn_with_command("Pwd Test", &shell, &[]).expect("Failed to spawn terminal");
tokio::time::sleep(Duration::from_millis($1)).await;
let command = "pwd";
for ch in command.chars() {
let key = KeyEvent::new(KeyCode::Char(ch), KeyModifiers::NONE);
term.handle_key(key);
tokio::time::sleep(Duration::from_millis($1)).await;
}
term.handle_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
tokio::time::sleep(Duration::from_millis($1)).await;
let lines = get_screen_text(&term);
assert!(!lines.is_empty(), "pwd command should produce output");
let has_path = lines.iter().any(|l| l.starts_with('/') || l.contains(':'));
assert!(has_path, "pwd should show directory path, got: {:?}", lines);
}
#[tokio::test]
async fn test_ls_command() {
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
let mut term = TermTui::spawn_with_command("Ls Test", &shell, &[])
.expect("Failed to spawn terminal");
tokio::time::sleep(Duration::from_millis(500)).await;
let command = "ls";
for ch in command.chars() {
let key = KeyEvent::new(KeyCode::Char(ch), KeyModifiers::NONE);
term.handle_key(key);
tokio::time::sleep(Duration::from_millis($1)).await;
}
term.handle_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
tokio::time::sleep(Duration::from_millis($1)).await;
let lines = get_screen_text(&term);
assert!(!lines.is_empty(), "ls command should produce output");
let expected_items = vec!["Cargo.toml", "src", "tests", "crates"];
let has_any = expected_items
.iter()
.any(|item| lines.iter().any(|line| line.contains(item)));
assert!(
has_any,
"ls should show some known files/dirs, got: {:?}",
lines
);
}
#[tokio::test]\nasync fn test_$1() {
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
let mut term = TermTui::spawn_with_command("Multi Command", &shell, &[])
.expect("Failed to spawn terminal");
tokio::time::sleep(Duration::from_millis($1)).await;
let cmd1 = "echo test1";
for ch in cmd1.chars() {
term.handle_key(KeyEvent::new(KeyCode::Char(ch), KeyModifiers::NONE));
tokio::time::sleep(Duration::from_millis($1)).await;
}
term.handle_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
tokio::time::sleep(Duration::from_millis($1)).await;
let cmd2 = "echo test2";
for ch in cmd2.chars() {
term.handle_key(KeyEvent::new(KeyCode::Char(ch), KeyModifiers::NONE));
tokio::time::sleep(Duration::from_millis($1)).await;
}
term.handle_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
let found1 = wait_for_output(&term, "test1", 1000);
let found2 = wait_for_output(&term, "test2", 1000);
assert!(found1, "Should find 'test1' in output");
assert!(found2, "Should find 'test2' in output");
}
#[tokio::test]\nasync fn test_$1() {
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
let mut term =
TermTui::spawn_with_command("Copy Mode", &shell, &[]).expect("Failed to spawn terminal");
tokio::time::sleep(Duration::from_millis($1)).await;
assert!(
!term.copy_mode.is_active(),
"Should not be in copy mode initially"
);
term.handle_key(KeyEvent::new(KeyCode::Char('x'), KeyModifiers::CONTROL));
assert!(term.copy_mode.is_active(), "Should enter copy mode");
term.handle_key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
assert!(!term.copy_mode.is_active(), "Should exit copy mode");
}
#[tokio::test]\nasync fn test_$1() {
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
let mut term =
TermTui::spawn_with_command("Copy Nav", &shell, &[]).expect("Failed to spawn terminal");
tokio::time::sleep(Duration::from_millis($1)).await;
term.handle_key(KeyEvent::new(KeyCode::Char('x'), KeyModifiers::CONTROL));
assert!(term.copy_mode.is_active());
term.handle_key(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE));
term.handle_key(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE));
term.handle_key(KeyEvent::new(KeyCode::Right, KeyModifiers::NONE));
term.handle_key(KeyEvent::new(KeyCode::Left, KeyModifiers::NONE));
assert!(
term.copy_mode.is_active(),
"Should remain in copy mode after navigation"
);
term.handle_key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
}
#[tokio::test]\nasync fn test_$1() {
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
let mut term =
TermTui::spawn_with_command("Various Cmds", &shell, &[]).expect("Failed to spawn terminal");
tokio::time::sleep(Duration::from_millis($1)).await;
let command = "date";
for ch in command.chars() {
term.handle_key(KeyEvent::new(KeyCode::Char(ch), KeyModifiers::NONE));
tokio::time::sleep(Duration::from_millis($1)).await;
}
term.handle_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
let found = wait_for_output(&term, "202", 2000)
|| wait_for_output(&term, "Mon", 2000)
|| wait_for_output(&term, "Tue", 2000)
|| wait_for_output(&term, "Wed", 2000)
|| wait_for_output(&term, "Thu", 2000)
|| wait_for_output(&term, "Fri", 2000)
|| wait_for_output(&term, "Sat", 2000)
|| wait_for_output(&term, "Sun", 2000);
assert!(found, "date command should produce recognizable output");
}
#[tokio::test]\nasync fn test_$1() {
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
let mut term =
TermTui::spawn_with_command("Focus Test", &shell, &[]).expect("Failed to spawn terminal");
assert!(!term.focused, "Terminal should not be focused initially");
term.focused = true;
assert!(term.focused, "Terminal should be focused");
term.handle_key(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE));
term.handle_key(KeyEvent::new(KeyCode::Char('b'), KeyModifiers::NONE));
term.handle_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
tokio::time::sleep(Duration::from_millis($1)).await;
let lines = get_screen_text(&term);
assert!(
!lines.is_empty(),
"Terminal should have content after typing"
);
}
#[tokio::test]\nasync fn test_$1() {
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
let mut term =
TermTui::spawn_with_command("Invalid Cmd", &shell, &[]).expect("Failed to spawn terminal");
tokio::time::sleep(Duration::from_millis($1)).await;
let command = "nonexistentcommand12345";
for ch in command.chars() {
term.handle_key(KeyEvent::new(KeyCode::Char(ch), KeyModifiers::NONE));
tokio::time::sleep(Duration::from_millis($1)).await;
}
term.handle_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
tokio::time::sleep(Duration::from_millis($1)).await;
let lines = get_screen_text(&term);
assert!(
!lines.is_empty(),
"Should have some response to invalid command"
);
}