#[cfg(test)]
#[path = "run_panel_support.rs"]
mod support;
#[cfg(test)]
#[path = "run_panel_worker.rs"]
mod worker;
use support::panel_app;
use super::run_panel::RunStepStatus;
use super::run_worker::{RunMsg, RunOutcome};
use saya_types::RunEvent;
use std::time::{Duration, Instant};
#[test]
fn a_run_driven_from_the_tui_renders_its_steps_and_updates_as_they_complete() {
let (mut app, tx, _cancel) = panel_app("r-panel-steps", "survey the data");
let (respond, answer) = tokio::sync::oneshot::channel();
tx.send(RunMsg::PlanApproval {
view_text: "run approval — goal: survey the data".into(),
steps: vec!["ask the data".into(), "write the note".into()],
respond,
})
.unwrap();
tx.send(RunMsg::Event(RunEvent::RunStarted)).unwrap();
app.poll_run_panel(false);
{
let panel = app.run_panel.as_ref().unwrap();
assert_eq!(panel.steps.len(), 2, "the plan's steps seeded the list");
assert_eq!(panel.steps[0].goal, "ask the data");
assert_eq!(panel.steps[0].status, RunStepStatus::Pending);
assert_eq!(panel.status, "run started");
assert!(
app.transcript.blocks().is_empty(),
"the run does not touch the conversation"
);
}
tx.send(RunMsg::Event(RunEvent::StepStarted { step: 0 }))
.unwrap();
app.poll_run_panel(false);
assert_eq!(
app.run_panel.as_ref().unwrap().steps[0].status,
RunStepStatus::Running
);
assert_eq!(app.run_panel.as_ref().unwrap().status, "step 1 started");
tx.send(RunMsg::Event(RunEvent::StepCompleted { step: 0 }))
.unwrap();
tx.send(RunMsg::Event(RunEvent::StepStarted { step: 1 }))
.unwrap();
app.poll_run_panel(false);
let panel = app.run_panel.as_ref().unwrap();
assert_eq!(panel.steps[0].status, RunStepStatus::Completed);
assert!(
panel.steps[0].elapsed.is_some(),
"the completed step froze its elapsed"
);
assert!(
panel.steps[0].started.is_none(),
"no live clock lingers on a done step"
);
assert_eq!(panel.steps[1].status, RunStepStatus::Running);
assert_eq!(panel.status, "step 2 started");
app.answer_plan_approval(true);
assert!(
answer.blocking_recv().unwrap(),
"the modal's yes reaches the drive's decision"
);
tx.send(RunMsg::Event(RunEvent::StepCompleted { step: 1 }))
.unwrap();
tx.send(RunMsg::Event(RunEvent::Completed)).unwrap();
tx.send(RunMsg::Done(RunOutcome {
code: 0,
message: String::new(),
}))
.unwrap();
app.poll_run_panel(false);
let panel = app.run_panel.as_ref().unwrap();
assert!(panel.terminated, "a terminal event marks the run over");
assert_eq!(panel.status, "run completed");
assert!(!panel.is_active(), "the worker is released once it ends");
assert!(
app.transcript.blocks().is_empty(),
"still nothing in the conversation"
);
}
#[test]
fn the_event_loop_is_not_blocked_while_a_run_executes() {
let (mut app, tx, _cancel) = panel_app("r-panel-resp", "long job");
let (release, wait) = std::sync::mpsc::channel();
std::thread::spawn(move || {
tx.send(RunMsg::Event(RunEvent::StepStarted { step: 0 }))
.unwrap();
wait.recv().unwrap();
tx.send(RunMsg::Done(RunOutcome {
code: 0,
message: String::new(),
}))
.unwrap();
});
let deadline = Instant::now() + Duration::from_secs(5);
loop {
let t0 = Instant::now();
app.poll_run_panel(false);
assert!(
t0.elapsed() < Duration::from_millis(100),
"the per-tick drain must not block on the worker (took {:?})",
t0.elapsed()
);
if app.run_panel.as_ref().unwrap().status == "step 1 started" {
break;
}
assert!(
Instant::now() < deadline,
"the worker's first event never landed"
);
std::thread::sleep(Duration::from_millis(10));
}
assert!(app.run_panel.as_ref().unwrap().is_active());
let t1 = Instant::now();
app.poll_run_panel(false);
assert!(
t1.elapsed() < Duration::from_millis(100),
"draining while the worker is in flight must stay prompt"
);
release.send(()).unwrap();
let deadline = Instant::now() + Duration::from_secs(5);
while app.run_panel.as_ref().unwrap().is_active() {
let t0 = Instant::now();
app.poll_run_panel(false);
assert!(
t0.elapsed() < Duration::from_millis(100),
"draining the end must be as prompt as draining progress"
);
assert!(Instant::now() < deadline, "the worker's end never landed");
std::thread::yield_now();
}
assert!(
!app.run_panel.as_ref().unwrap().is_active(),
"the run's end was applied by a non-blocking poll"
);
}