use egui_async::{
Bind, State, StateWithData,
bind::{CURR_FRAME, LAST_FRAME},
};
use std::sync::{Mutex, OnceLock};
fn test_lock() -> &'static Mutex<()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(|| Mutex::new(()))
}
fn with_lock<T>(f: impl FnOnce() -> T) -> T {
let _keep = match test_lock().lock() {
Ok(g) => g,
Err(p) => p.into_inner(),
};
f()
}
fn set_time(curr: f64, last: f64) {
CURR_FRAME.store(curr, std::sync::atomic::Ordering::Relaxed);
LAST_FRAME.store(last, std::sync::atomic::Ordering::Relaxed);
}
fn advance_frame(seconds: f64) {
let current = CURR_FRAME.load(std::sync::atomic::Ordering::Relaxed);
CURR_FRAME.store(current + seconds, std::sync::atomic::Ordering::Relaxed);
}
#[test]
fn debug_impls_coverage() {
with_lock(|| {
let idle: StateWithData<i32, i32> = StateWithData::Idle;
assert_eq!(format!("{idle:?}"), "Idle");
let pending: StateWithData<i32, i32> = StateWithData::Pending;
assert_eq!(format!("{pending:?}"), "Pending");
let finished: StateWithData<i32, i32> = StateWithData::Finished(&10);
assert_eq!(format!("{finished:?}"), "Finished(10)");
let failed: StateWithData<i32, i32> = StateWithData::Failed(&500);
assert_eq!(format!("{failed:?}"), "Failed(500)");
let b_empty: Bind<i32, i32> = Bind::default();
let dbg = format!("{b_empty:?}");
assert!(dbg.contains("state: Idle"));
assert!(dbg.contains("data: \"None\""));
assert!(dbg.contains("in_flight: \"None\""));
let mut b_full: Bind<i32, ()> = Bind::new(false);
b_full.fill(Ok(42));
let dbg_full = format!("{b_full:?}");
assert!(dbg_full.contains("state: Finished"));
assert!(dbg_full.contains("data: \"Some(...)\""));
let mut b_pending: Bind<i32, i32> = Bind::new(false);
b_pending.request(async { Ok(1) });
let dbg_pending = format!("{b_pending:?}");
assert!(dbg_pending.contains("state: Pending"));
assert!(dbg_pending.contains("in_flight: InFlight"));
});
}
#[test]
fn getters_and_setters_coverage() {
with_lock(|| {
set_time(10.0, 9.0);
let mut b: Bind<i32, String> = Bind::new(false);
assert!(!b.retain());
b.set_retain(true);
assert!(b.retain());
b.fill(Ok(100));
assert_eq!(b.read_as_ref(), Some(Ok(&100)));
assert!(b.read_mut().is_some());
if let Some(Ok(val)) = b.read_as_mut() {
*val += 1;
}
assert_eq!(b.ok_ref(), Some(&101));
assert!(b.err_ref().is_none());
b.clear();
b.fill(Err("error".to_string()));
if let Some(Err(e)) = b.read_as_mut() {
e.push('!');
}
assert_eq!(b.err_ref(), Some(&"error!".to_string()));
assert!(b.ok_ref().is_none());
assert!(b.take_ok().is_none());
assert!(b.is_err());
b.clear();
b.fill(Ok(999));
assert_eq!(b.take_ok(), Some(999));
assert!(b.is_idle()); });
}
#[test]
fn retain_true_does_not_clear() {
with_lock(|| {
set_time(0.0, -1.0);
let mut b: Bind<i32, ()> = Bind::new(true); b.fill(Ok(1));
b.poll();
assert!(b.is_finished());
set_time(2.0, 1.0);
b.poll(); assert!(b.is_finished());
assert!(b.read().is_some());
});
}
#[test]
fn poll_early_exit_optimization() {
with_lock(|| {
set_time(10.0, 9.0);
let mut b: Bind<i32, ()> = Bind::new(false);
b.fill(Ok(1));
b.poll();
assert!(b.was_drawn_this_frame());
let prev = b.was_drawn_last_frame();
b.poll(); assert_eq!(b.was_drawn_last_frame(), prev);
});
}
#[test]
fn on_finished_callback() {
with_lock(|| {
set_time(10.0, 9.0);
let mut b: Bind<i32, ()> = Bind::new(false);
let mut called = false;
b.on_finished(|_| called = true);
assert!(!called);
b.fill(Ok(10));
b.on_finished(|res| {
called = true;
assert_eq!(res.as_ref().expect("should be ok"), &10);
});
assert!(called);
set_time(11.0, 10.0);
called = false;
b.on_finished(|_| called = true);
assert!(!called);
});
}
#[test]
fn state_method_unreachable_guard() {
let mut b: Bind<i32, i32> = Bind::new(true);
assert!(matches!(b.state(), StateWithData::Idle));
b.request(std::future::pending::<Result<i32, i32>>());
assert!(matches!(b.state(), StateWithData::Pending));
}
#[test]
fn read_mut_or_request_logic() {
with_lock(|| {
set_time(0.0, -1.0);
let mut b: Bind<i32, ()> = Bind::new(true);
let res = b.read_mut_or_request(std::future::pending::<Result<i32, ()>>);
assert!(res.is_none());
assert!(b.is_pending());
b.clear();
b.fill(Ok(10));
if let Some(Ok(val)) = b.read_mut_or_request(|| async { panic!("Should not run") }) {
*val = 20;
}
assert_eq!(b.ok_ref(), Some(&20));
});
}
#[test]
fn poll_handle_closed_channel() {
with_lock(|| {
let mut curr_time = 5.0;
set_time(curr_time, 4.0);
let mut b: Bind<i32, ()> = Bind::new(false);
b.request(async {
panic!("Force drop sender");
});
let start = std::time::Instant::now();
loop {
assert!(
start.elapsed() <= std::time::Duration::from_secs(2),
"Timed out waiting for Bind to handle closed channel"
);
curr_time += 1.0;
CURR_FRAME.store(curr_time, std::sync::atomic::Ordering::Relaxed);
b.poll();
if b.get_state() == State::Idle {
break;
}
std::thread::sleep(std::time::Duration::from_millis(5));
}
assert_eq!(b.get_state(), State::Idle);
assert!(b.read().is_none());
});
}
#[allow(clippy::float_cmp)]
#[tokio::test]
async fn test_fill_from_idle() {
let mut bind: Bind<i32, ()> = Bind::new(true);
bind.fill(Ok(42));
assert!(bind.is_finished());
assert_eq!(bind.read_as_ref(), Some(Ok(&42)));
assert_eq!(
bind.get_elapsed(),
0.0,
"Elapsed time for immediate fill should be 0.0"
);
}
#[tokio::test]
async fn test_fill_overwrites_pending_and_aborts() {
let mut bind: Bind<i32, ()> = Bind::new(true);
bind.request(async {
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
Ok(999)
});
assert!(bind.is_pending(), "Bind should be pending initially");
bind.fill(Ok(100));
assert!(bind.is_finished(), "State should be Finished");
assert!(!bind.is_pending(), "Pending state should be cleared");
assert_eq!(
bind.read_as_ref(),
Some(Ok(&100)),
"Data should be updated to filled value"
);
bind.poll();
assert_eq!(
bind.read_as_ref(),
Some(Ok(&100)),
"Old task result must not overwrite filled data"
);
}
#[allow(clippy::float_cmp)]
#[tokio::test]
async fn test_fill_overwrites_finished() {
let mut bind: Bind<i32, ()> = Bind::new(true);
bind.fill(Ok(1));
assert_eq!(bind.read_as_ref(), Some(Ok(&1)));
bind.fill(Ok(2));
assert!(bind.is_finished());
assert_eq!(bind.read_as_ref(), Some(Ok(&2)));
assert_eq!(
bind.get_start_time(),
bind.get_complete_time(),
"Fill should update start and complete times to the same instant"
);
}
#[allow(clippy::float_cmp)]
#[tokio::test]
async fn test_fill_updates_timestamps() {
let mut bind: Bind<&str, ()> = Bind::new(false);
advance_frame(1.0);
let start_time = CURR_FRAME.load(std::sync::atomic::Ordering::Relaxed);
bind.fill(Ok("test"));
assert_eq!(bind.get_start_time(), start_time);
assert_eq!(bind.get_complete_time(), start_time);
assert_eq!(bind.since_started(), 0.0);
advance_frame(0.5);
assert_eq!(bind.since_started(), 0.5);
assert_eq!(bind.since_completed(), 0.5);
}