use std::collections::VecDeque;
use ratatui::text::Line;
use super::*;
use crate::tui::tests::test_app;
fn prompt(text: &str) -> QueuedPrompt {
QueuedPrompt {
prompt: text.into(),
display_prompt: text.into(),
paste_segments: Vec::new(),
}
}
fn key(code: KeyCode, modifiers: KeyModifiers) -> KeyEvent {
KeyEvent::new(code, modifiers)
}
fn line_text(line: &Line<'_>) -> String {
line.spans
.iter()
.map(|span| span.content.as_ref())
.collect()
}
#[test]
fn alt_up_prioritizes_latest_local_steer_over_follow_up() {
let mut app = test_app();
app.pending.push_follow_up(prompt("future turn"));
app.pending
.steering_prompts_mut()
.push_back(prompt("first steer"));
app.pending
.steering_prompts_mut()
.push_back(prompt("latest steer"));
assert!(app.handle_pending_input_key(key(KeyCode::Up, KeyModifiers::ALT)));
assert_eq!(app.input_ui.text(), "latest steer");
assert_eq!(
*app.pending.steering_prompts(),
VecDeque::from([prompt("first steer")])
);
assert_eq!(
*app.pending.queued_prompts(),
VecDeque::from([prompt("future turn")])
);
}
#[test]
fn alt_up_requests_retraction_for_accepted_steer() {
let mut app = test_app();
let id = rho_sdk::SteeringId::new();
app.pending
.accepted_steering_mut()
.push_back(AcceptedSteering {
id: id.clone(),
prompt: prompt("retract me"),
});
app.handle_pending_input_key(key(KeyCode::Up, KeyModifiers::ALT));
assert!(matches!(
app.pending.input_action(),
Some(PendingInputAction::EditAccepted {
id: ref action_id,
..
}) if action_id == &id
));
assert!(app.input_ui.text().is_empty());
assert_eq!(app.pending.accepted_steering().len(), 1);
}
#[test]
fn alt_up_preserves_nonempty_composer() {
let mut app = test_app();
app.input_ui.set_text("draft".to_string());
app.input_ui.set_cursor(app.input_char_len());
app.pending.push_follow_up(prompt("future turn"));
app.handle_pending_input_key(key(KeyCode::Up, KeyModifiers::ALT));
assert_eq!(app.input_ui.text(), "draft");
assert_eq!(
*app.pending.queued_prompts(),
VecDeque::from([prompt("future turn")])
);
assert_eq!(
app.history.last_status_notice(),
Some("clear the composer before editing pending input")
);
}
#[test]
fn applied_event_preserves_selection_of_a_later_pending_item() {
let mut app = test_app();
let applied = rho_sdk::SteeringId::new();
app.pending
.accepted_steering_mut()
.push_back(AcceptedSteering {
id: applied.clone(),
prompt: prompt("first steer"),
});
app.pending
.accepted_steering_mut()
.push_back(AcceptedSteering {
id: rho_sdk::SteeringId::new(),
prompt: prompt("second steer"),
});
app.pending.push_follow_up(prompt("future turn"));
app.pending.input_panel_mut().selected = 2;
app.mark_steering_applied(&[applied]);
assert_eq!(app.pending.input_panel().selected, 1);
let lines = app.pending_input_lines(80);
assert!(line_text(&lines[2]).contains("▸ NEXT"));
}
#[test]
fn backspace_removes_the_selected_follow_up() {
let mut app = test_app();
app.pending.push_follow_up(prompt("first"));
app.pending.push_follow_up(prompt("second"));
app.select_pending_recall_target();
app.handle_pending_input_key(key(KeyCode::Char('q'), KeyModifiers::ALT));
app.handle_pending_input_key(key(KeyCode::Up, KeyModifiers::NONE));
app.handle_pending_input_key(key(KeyCode::Backspace, KeyModifiers::NONE));
assert_eq!(
*app.pending.queued_prompts(),
VecDeque::from([prompt("second")])
);
}
#[test]
fn rejected_steering_acceptance_becomes_a_follow_up_without_failing_the_turn() {
let mut app = test_app();
let queued = prompt("continue after this turn");
let request = PendingInputRequest::Accept {
prompt: queued.clone(),
receipt: Box::pin(std::future::pending()),
};
let completion = PendingInputCompletion::Accepted(Err(rho_sdk::Error::InvalidHostResponse {
message: "run completed before accepting steering input".into(),
}));
let failure = app.finish_pending_input_request(request, completion);
assert_eq!(failure, None);
assert!(app.pending.steering_prompts().is_empty());
assert_eq!(*app.pending.queued_prompts(), VecDeque::from([queued]));
assert_eq!(
app.history.last_status_notice(),
Some(
"steer queued as follow-up: invalid host response: run completed before accepting steering input"
)
);
}
#[test]
fn applied_event_removes_only_matching_steering() {
let mut app = test_app();
let applied = rho_sdk::SteeringId::new();
let pending = rho_sdk::SteeringId::new();
app.pending
.accepted_steering_mut()
.push_back(AcceptedSteering {
id: applied.clone(),
prompt: prompt("applied"),
});
app.pending
.accepted_steering_mut()
.push_back(AcceptedSteering {
id: pending.clone(),
prompt: prompt("pending"),
});
app.mark_steering_applied(&[applied]);
assert_eq!(app.pending.accepted_steering().len(), 1);
assert_eq!(app.pending.accepted_steering()[0].id, pending);
}