use lv_tui::prelude::*;
use lv_tui::event::WorkerId;
use lv_tui::Component;
#[derive(Component)]
struct SimpleWorker {
#[reactive(paint, copy)]
status: i32,
}
impl SimpleWorker {
fn new() -> Self { Self { status: 0 } }
}
impl Component for SimpleWorker {
fn render(&self, cx: &mut RenderCx) {
cx.line(&format!("status: {}", self.get_status()));
}
fn mount(&mut self, cx: &mut EventCx) {
self.set_status(1, cx);
cx.spawn(move || {
std::thread::sleep(std::time::Duration::from_millis(10));
"done".to_string()
});
}
fn event(&mut self, event: &Event, cx: &mut EventCx) {
if let Event::TaskComplete(ref result) = event {
if result == "done" {
self.set_status(2, cx);
}
}
}
}
#[test]
fn spawn_completes_and_delivers_result() {
let mut pilot = Pilot::new(SimpleWorker::new(), 20, 3);
let done = pilot.run_until(100, |buf| {
buf.cells.iter().any(|c| c.symbol == "2")
}).unwrap();
assert!(done, "spawned task should complete and update status");
}
#[derive(Component)]
struct MultiSpawn {
#[reactive(paint, copy)]
count: i32,
}
impl MultiSpawn {
fn new() -> Self { Self { count: 0 } }
}
impl Component for MultiSpawn {
fn render(&self, cx: &mut RenderCx) {
cx.line(&format!("count: {}", self.get_count()));
}
fn mount(&mut self, cx: &mut EventCx) {
for _ in 0..3 {
cx.spawn(move || {
std::thread::sleep(std::time::Duration::from_millis(5));
"ok".to_string()
});
}
}
fn event(&mut self, event: &Event, cx: &mut EventCx) {
if let Event::TaskComplete(ref result) = event {
if result == "ok" {
self.set_count(self.get_count() + 1, cx);
}
}
}
}
#[test]
fn multiple_spawns_all_complete() {
let mut pilot = Pilot::new(MultiSpawn::new(), 20, 3);
let done = pilot.run_until(100, |buf| {
buf.cells.iter().any(|c| c.symbol == "3")
}).unwrap();
assert!(done, "all 3 spawned tasks should complete");
}
#[derive(Component)]
struct PanicWorker {
#[reactive(paint, copy)]
status: i32,
}
impl PanicWorker {
fn new() -> Self { Self { status: 0 } }
}
impl Component for PanicWorker {
fn render(&self, cx: &mut RenderCx) {
cx.line(&format!("status: {}", self.get_status()));
}
fn mount(&mut self, cx: &mut EventCx) {
cx.spawn(move || "completed".to_string());
}
fn event(&mut self, event: &Event, cx: &mut EventCx) {
if let Event::TaskComplete(ref result) = event {
if !result.is_empty() {
self.set_status(1, cx);
}
}
}
}
#[test]
fn worker_completes_gracefully() {
let mut pilot = Pilot::new(PanicWorker::new(), 20, 3);
let done = pilot.run_until(100, |buf| {
buf.cells.iter().any(|c| c.symbol == "1")
}).unwrap();
assert!(done, "worker should complete and deliver result");
}
#[derive(Component)]
struct KeyTriggered {
#[reactive(paint, copy)]
result: i32,
}
impl KeyTriggered {
fn new() -> Self { Self { result: 0 } }
}
impl Component for KeyTriggered {
fn render(&self, cx: &mut RenderCx) {
cx.line(&format!("result: {}", self.get_result()));
}
fn event(&mut self, event: &Event, cx: &mut EventCx) {
match event {
Event::Key(key_event) if key_event.key == Key::Char('r') => {
cx.spawn(move || "triggered".to_string());
}
Event::TaskComplete(ref result) => {
if result == "triggered" {
self.set_result(1, cx);
}
}
_ => {}
}
}
}
#[test]
fn spawn_triggered_by_key() {
let mut pilot = Pilot::new(KeyTriggered::new(), 20, 3);
pilot.press(Key::Char('r')).unwrap();
let done = pilot.run_until(100, |buf| {
buf.cells.iter().any(|c| c.symbol == "1")
}).unwrap();
assert!(done, "spawn triggered by key should complete");
}
#[derive(Component)]
struct DataWorker {
#[reactive(paint, copy)]
data: i32,
}
impl DataWorker {
fn new() -> Self { Self { data: 0 } }
}
impl Component for DataWorker {
fn render(&self, cx: &mut RenderCx) {
cx.line(&format!("data: {}", self.get_data()));
}
fn mount(&mut self, cx: &mut EventCx) {
cx.spawn(move || "42".to_string());
}
fn event(&mut self, event: &Event, cx: &mut EventCx) {
if let Event::TaskComplete(ref result) = event {
if let Ok(val) = result.parse::<i32>() {
self.set_data(val, cx);
}
}
}
}
#[test]
fn spawn_passes_data_back() {
let mut pilot = Pilot::new(DataWorker::new(), 20, 3);
let done = pilot.run_until(100, |buf| {
buf.cells.iter().any(|c| c.symbol == "4")
}).unwrap();
assert!(done, "task should pass data back via TaskComplete");
}
#[test]
fn worker_id_is_unique() {
let id1 = WorkerId(1);
let id2 = WorkerId(2);
assert_ne!(id1, id2);
assert_eq!(id1, WorkerId(1));
}
#[derive(Component)]
struct TrackedWorker {
#[reactive(paint, copy)]
done: i32,
}
impl TrackedWorker {
fn new() -> Self { Self { done: 0 } }
}
impl Component for TrackedWorker {
fn render(&self, cx: &mut RenderCx) {
cx.line(&format!("done: {}", self.get_done()));
}
fn mount(&mut self, cx: &mut EventCx) {
cx.spawn_worker(move || {
std::thread::sleep(std::time::Duration::from_millis(5));
"tracked".to_string()
});
}
fn event(&mut self, event: &Event, cx: &mut EventCx) {
if let Event::WorkerDone(_, ref payload) = event {
if payload == "tracked" {
self.set_done(1, cx);
}
}
}
}
#[test]
fn spawn_worker_delivers_workerdone() {
let mut pilot = Pilot::new(TrackedWorker::new(), 20, 3);
let done = pilot.run_until(100, |buf| {
buf.cells.iter().any(|c| c.symbol == "1")
}).unwrap();
assert!(done, "spawn_worker should deliver WorkerDone event");
}
#[derive(Component)]
struct CancellableWorker {
#[reactive(paint, copy)]
result: i32,
}
impl CancellableWorker {
fn new() -> Self { Self { result: 0 } }
}
impl Component for CancellableWorker {
fn render(&self, cx: &mut RenderCx) {
cx.line(&format!("result: {}", self.get_result()));
}
fn mount(&mut self, cx: &mut EventCx) {
let id = cx.spawn_worker(move || {
std::thread::sleep(std::time::Duration::from_millis(500));
"not_coming".to_string()
});
cx.cancel_worker(id);
cx.set_timer(20); }
fn event(&mut self, event: &Event, cx: &mut EventCx) {
match event {
Event::WorkerDone(_, ref payload) => {
if payload.is_empty() {
self.set_result(1, cx); } else {
self.set_result(2, cx); }
}
Event::Timer(_) => {
if self.get_result() == 0 {
self.set_result(3, cx); }
}
_ => {}
}
}
}
#[test]
fn cancel_worker_stops_execution() {
let mut pilot = Pilot::new(CancellableWorker::new(), 20, 3);
let updated = pilot.run_until(100, |buf| {
buf.cells.iter().any(|c| c.symbol == "1" || c.symbol == "3")
}).unwrap();
assert!(updated, "cancel should produce cancelled WorkerDone or timeout");
assert!(!pilot.frame().cells.iter().any(|c| c.symbol == "2"),
"cancelled worker should not complete normally");
}