use color_eyre::eyre::Result;
use ratatui::Frame;
use ratatui::Terminal;
use ratatui::backend::TestBackend;
use tears::prelude::*;
use tokio::time::{Duration, Instant, sleep, timeout};
#[tokio::test]
async fn test_quit_responsiveness_low_framerate() -> Result<()> {
let backend = TestBackend::new(80, 24);
let mut terminal = Terminal::new(backend)?;
let runtime = Runtime::<InitQuitApp>::new((), 16);
let start = Instant::now();
let result = timeout(Duration::from_millis(200), runtime.run(&mut terminal)).await?;
let elapsed = start.elapsed();
assert!(result.is_ok(), "Runtime should complete without error");
println!("Quit with low framerate took: {elapsed:?}");
assert!(
elapsed < Duration::from_millis(150),
"Should quit quickly even with low framerate"
);
Ok(())
}
struct InitQuitApp;
impl Application for InitQuitApp {
type Message = ();
type Flags = ();
fn new(_flags: ()) -> (Self, Command<Self::Message>) {
(Self, Command::effect(Action::Quit))
}
fn update(&mut self, _msg: Self::Message) -> Command<Self::Message> {
Command::none()
}
fn view(&self, _frame: &mut Frame<'_>) {}
fn subscriptions(&self) -> Vec<Subscription<Self::Message>> {
vec![]
}
}
#[tokio::test]
async fn test_quit_from_init_command() -> Result<()> {
let backend = TestBackend::new(80, 24);
let mut terminal = Terminal::new(backend)?;
let runtime = Runtime::<InitQuitApp>::new((), 60);
let start = Instant::now();
let result = timeout(Duration::from_secs(1), runtime.run(&mut terminal)).await?;
let elapsed = start.elapsed();
assert!(result.is_ok(), "Runtime should complete without error");
println!("Init quit took: {elapsed:?}");
assert!(
elapsed < Duration::from_millis(200),
"Should quit quickly from init command"
);
Ok(())
}
struct DelayedQuitApp;
impl Application for DelayedQuitApp {
type Message = ();
type Flags = ();
fn new(_flags: ()) -> (Self, Command<Self::Message>) {
let cmd = Command::future(async {
sleep(Duration::from_millis(50)).await;
});
(Self, cmd)
}
fn update(&mut self, _msg: Self::Message) -> Command<Self::Message> {
Command::effect(Action::Quit)
}
fn view(&self, _frame: &mut Frame<'_>) {}
fn subscriptions(&self) -> Vec<Subscription<Self::Message>> {
vec![]
}
}
#[tokio::test]
async fn test_quit_during_frame_wait() -> Result<()> {
let backend = TestBackend::new(80, 24);
let mut terminal = Terminal::new(backend)?;
let runtime = Runtime::<DelayedQuitApp>::new((), 10);
let start = Instant::now();
let result = timeout(Duration::from_millis(300), runtime.run(&mut terminal)).await?;
let elapsed = start.elapsed();
assert!(result.is_ok(), "Runtime should complete without error");
println!("Delayed quit took: {elapsed:?}");
assert!(
elapsed < Duration::from_millis(200),
"Should quit quickly even during frame wait"
);
Ok(())
}
struct MultiMessageQuitApp {
counter: u32,
}
#[derive(Debug, Clone)]
enum MultiMessage {
Increment,
Quit,
}
impl Application for MultiMessageQuitApp {
type Message = MultiMessage;
type Flags = ();
fn new(_flags: ()) -> (Self, Command<Self::Message>) {
let commands = vec![
Command::future(async { MultiMessage::Increment }),
Command::future(async { MultiMessage::Increment }),
Command::future(async { MultiMessage::Increment }),
Command::future(async { MultiMessage::Quit }),
];
(Self { counter: 0 }, Command::batch(commands))
}
fn update(&mut self, msg: Self::Message) -> Command<Self::Message> {
match msg {
MultiMessage::Increment => {
self.counter += 1;
Command::none()
}
MultiMessage::Quit => Command::effect(Action::Quit),
}
}
fn view(&self, _frame: &mut Frame<'_>) {}
fn subscriptions(&self) -> Vec<Subscription<Self::Message>> {
vec![]
}
}
#[tokio::test]
async fn test_quit_after_multiple_messages() -> Result<()> {
let backend = TestBackend::new(80, 24);
let mut terminal = Terminal::new(backend)?;
let runtime = Runtime::<MultiMessageQuitApp>::new((), 60);
let start = Instant::now();
let result = timeout(Duration::from_millis(500), runtime.run(&mut terminal)).await?;
let elapsed = start.elapsed();
assert!(result.is_ok(), "Runtime should complete without error");
println!("Multi-message quit took: {elapsed:?}");
assert!(
elapsed < Duration::from_millis(300),
"Should process messages and quit quickly"
);
Ok(())
}