use super::*;
use crate::app::Command;
use crate::app::command::BoxedError;
use std::time::Duration;
#[tokio::test]
async fn test_runtime_async_command() {
let mut runtime: Runtime<CounterApp, _> = Runtime::virtual_builder(80, 24).build().unwrap();
let cmd = Command::perform_async(async { Some(CounterMsg::IncrementBy(5)) });
runtime.commands.execute(cmd);
runtime.spawn_pending_commands();
tokio::time::sleep(Duration::from_millis(10)).await;
runtime.process_pending();
assert_eq!(runtime.state().count, 5);
}
#[tokio::test]
async fn test_runtime_message_channel() {
let mut runtime: Runtime<CounterApp, _> = Runtime::virtual_builder(80, 24).build().unwrap();
let sender = runtime.message_sender();
sender.send(CounterMsg::Increment).await.unwrap();
sender.send(CounterMsg::Increment).await.unwrap();
runtime.process_pending();
assert_eq!(runtime.state().count, 2);
}
#[tokio::test]
async fn test_runtime_take_errors() {
let mut runtime: Runtime<CounterApp, _> = Runtime::virtual_builder(80, 24).build().unwrap();
let error_tx = runtime.error_sender();
let errors = runtime.take_errors();
assert!(errors.is_empty());
let err: BoxedError = Box::new(std::io::Error::other("test error"));
error_tx.send(err).await.unwrap();
let errors = runtime.take_errors();
assert_eq!(errors.len(), 1);
assert!(errors[0].to_string().contains("test error"));
let errors = runtime.take_errors();
assert!(errors.is_empty());
}
#[tokio::test]
async fn test_runtime_has_errors() {
let mut runtime: Runtime<CounterApp, _> = Runtime::virtual_builder(80, 24).build().unwrap();
let error_tx = runtime.error_sender();
assert!(!runtime.has_errors());
let err: BoxedError = Box::new(std::io::Error::other("test error"));
error_tx.send(err).await.unwrap();
tokio::time::sleep(Duration::from_millis(1)).await;
assert!(runtime.has_errors());
let _ = runtime.take_errors();
assert!(!runtime.has_errors());
}
#[tokio::test]
async fn test_runtime_error_from_spawned_task() {
let mut runtime: Runtime<CounterApp, _> = Runtime::virtual_builder(80, 24).build().unwrap();
let error_tx = runtime.error_sender();
tokio::spawn(async move {
let err: BoxedError = Box::new(std::io::Error::new(
std::io::ErrorKind::NotFound,
"resource not found",
));
let _ = error_tx.send(err).await;
});
tokio::time::sleep(Duration::from_millis(10)).await;
let errors = runtime.take_errors();
assert_eq!(errors.len(), 1);
assert!(errors[0].to_string().contains("resource not found"));
}
#[tokio::test]
async fn test_runtime_multiple_errors() {
let mut runtime: Runtime<CounterApp, _> = Runtime::virtual_builder(80, 24).build().unwrap();
let error_tx = runtime.error_sender();
for i in 0..3 {
let err: BoxedError = Box::new(std::io::Error::other(format!("error {}", i)));
error_tx.send(err).await.unwrap();
}
let errors = runtime.take_errors();
assert_eq!(errors.len(), 3);
}
struct FallibleApp;
#[derive(Clone, Default)]
struct FallibleState {
value: Option<i32>,
}
#[derive(Clone, Debug)]
enum FallibleMsg {
FetchSuccess,
FetchFailure,
Loaded(i32),
}
impl App for FallibleApp {
type State = FallibleState;
type Message = FallibleMsg;
fn init() -> (Self::State, Command<Self::Message>) {
(FallibleState::default(), Command::none())
}
fn update(state: &mut Self::State, msg: Self::Message) -> Command<Self::Message> {
match msg {
FallibleMsg::FetchSuccess => {
Command::try_perform_async(async { Ok::<_, std::io::Error>(42) }, |n| {
Some(FallibleMsg::Loaded(n))
})
}
FallibleMsg::FetchFailure => Command::try_perform_async(
async {
Err::<i32, _>(std::io::Error::new(
std::io::ErrorKind::NotFound,
"data not found",
))
},
|n| Some(FallibleMsg::Loaded(n)),
),
FallibleMsg::Loaded(n) => {
state.value = Some(n);
Command::none()
}
}
}
fn view(_state: &Self::State, _frame: &mut ratatui::Frame) {}
}
#[tokio::test]
async fn test_runtime_try_perform_async_success() {
let mut runtime: Runtime<FallibleApp, _> = Runtime::virtual_builder(80, 24).build().unwrap();
runtime.dispatch(FallibleMsg::FetchSuccess);
tokio::time::sleep(Duration::from_millis(20)).await;
runtime.process_pending();
assert_eq!(runtime.state().value, Some(42));
assert!(!runtime.has_errors());
}
#[tokio::test]
async fn test_runtime_try_perform_async_failure() {
let mut runtime: Runtime<FallibleApp, _> = Runtime::virtual_builder(80, 24).build().unwrap();
runtime.dispatch(FallibleMsg::FetchFailure);
tokio::time::sleep(Duration::from_millis(20)).await;
runtime.process_pending();
assert_eq!(runtime.state().value, None);
let errors = runtime.take_errors();
assert_eq!(errors.len(), 1);
assert!(errors[0].to_string().contains("data not found"));
}
#[tokio::test]
async fn test_runtime_subscribe() {
use crate::app::subscription::TickSubscription;
let mut runtime: Runtime<CounterApp, _> = Runtime::virtual_builder(80, 24).build().unwrap();
let sub = TickSubscription::new(Duration::from_millis(10), || CounterMsg::Increment);
runtime.subscribe(sub);
let tx = runtime.message_sender();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(50)).await;
let _ = tx.send(CounterMsg::Quit).await;
});
runtime.run().await.unwrap();
assert!(runtime.should_quit());
}
#[tokio::test]
async fn test_runtime_subscribe_all() {
use crate::app::subscription::{BoxedSubscription, TickSubscription};
let mut runtime: Runtime<CounterApp, _> = Runtime::virtual_builder(80, 24).build().unwrap();
let sub1: BoxedSubscription<CounterMsg> =
Box::new(TickSubscription::new(Duration::from_millis(10), || {
CounterMsg::Increment
}));
let sub2: BoxedSubscription<CounterMsg> =
Box::new(TickSubscription::new(Duration::from_millis(10), || {
CounterMsg::Increment
}));
runtime.subscribe_all(vec![sub1, sub2]);
tokio::time::sleep(Duration::from_millis(50)).await;
runtime.quit();
}
#[tokio::test]
async fn test_runtime_run() {
let mut runtime: Runtime<CounterApp, _> = Runtime::virtual_builder(40, 10).build().unwrap();
runtime.dispatch(CounterMsg::Increment);
let tx = runtime.message_sender();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(50)).await;
let _ = tx.send(CounterMsg::Quit).await;
});
runtime.run().await.unwrap();
assert!(runtime.should_quit());
assert!(runtime.contains_text("Count: 1"));
}
#[tokio::test]
async fn test_runtime_run_cancelled() {
let mut runtime: Runtime<CounterApp, _> = Runtime::virtual_builder(80, 24).build().unwrap();
let token = runtime.cancellation_token();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(50)).await;
token.cancel();
});
runtime.run().await.unwrap();
assert!(runtime.should_quit());
}
struct InitCommandApp;
#[derive(Clone, Default)]
struct InitCommandState {
initialized: bool,
}
#[derive(Clone)]
enum InitCommandMsg {
Initialized,
}
impl App for InitCommandApp {
type State = InitCommandState;
type Message = InitCommandMsg;
fn init() -> (Self::State, Command<Self::Message>) {
(
InitCommandState::default(),
Command::message(InitCommandMsg::Initialized),
)
}
fn update(state: &mut Self::State, msg: Self::Message) -> Command<Self::Message> {
match msg {
InitCommandMsg::Initialized => state.initialized = true,
}
Command::none()
}
fn view(_state: &Self::State, _frame: &mut ratatui::Frame) {}
}
#[test]
fn test_runtime_init_command() {
let mut runtime: Runtime<InitCommandApp, _> = Runtime::virtual_builder(80, 24).build().unwrap();
runtime.process_pending();
assert!(runtime.state().initialized);
}
struct TickingApp;
#[derive(Clone, Default)]
struct TickingState {
ticks: i32,
quit: bool,
}
#[derive(Clone)]
enum TickingMsg {
Tick,
}
impl App for TickingApp {
type State = TickingState;
type Message = TickingMsg;
fn init() -> (Self::State, Command<Self::Message>) {
(TickingState::default(), Command::none())
}
fn update(state: &mut Self::State, msg: Self::Message) -> Command<Self::Message> {
match msg {
TickingMsg::Tick => {
state.ticks += 1;
if state.ticks >= 3 {
state.quit = true;
}
}
}
Command::none()
}
fn view(_state: &Self::State, _frame: &mut ratatui::Frame) {}
fn should_quit(state: &Self::State) -> bool {
state.quit
}
fn on_tick(_state: &Self::State) -> Option<Self::Message> {
Some(TickingMsg::Tick)
}
}
#[test]
fn test_runtime_ticking_app() {
let mut runtime: Runtime<TickingApp, _> = Runtime::virtual_builder(80, 24).build().unwrap();
runtime.tick().unwrap();
assert_eq!(runtime.state().ticks, 1);
runtime.tick().unwrap();
assert_eq!(runtime.state().ticks, 2);
runtime.tick().unwrap();
assert_eq!(runtime.state().ticks, 3);
assert!(runtime.should_quit());
}
#[tokio::test]
async fn test_runtime_run_with_on_tick() {
let mut runtime: Runtime<TickingApp, _> = Runtime::virtual_builder(80, 24).build().unwrap();
runtime.run().await.unwrap();
assert!(runtime.should_quit());
assert!(runtime.state().ticks >= 3);
}