rho-coding-agent 2.9.1

A fast Rust agent harness with a small footprint and opinionated defaults
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use rho_sdk::{HostChoice, HostInputRequest, HostInputResponse, HostQuestion, SelectionMode};

use crate::tui::questionnaire::QuestionnaireComposer;

use super::*;

fn choice(label: &str) -> HostChoice {
    HostChoice::new(label, label)
}

fn choice_question(id: &str) -> HostQuestion {
    HostQuestion::new(
        id,
        format!("{id}?"),
        vec![choice("alpha"), choice("beta")],
        SelectionMode::One,
    )
    .unwrap()
}

fn confirm_question(id: &str) -> HostQuestion {
    HostQuestion::new(
        id,
        format!("{id}?"),
        vec![HostChoice::new("yes", "yes"), HostChoice::new("no", "no")],
        SelectionMode::One,
    )
    .unwrap()
}

fn host_request(questions: Vec<HostQuestion>) -> HostInputRequest {
    HostInputRequest::questionnaire("", questions).unwrap()
}

#[test]
fn enter_advances_questions_and_submits_only_on_the_last() {
    let (reply_tx, mut reply_rx) = tokio::sync::oneshot::channel();
    let mut app = test_app();
    app.input_ui
        .set_composer(ComposerMode::Questionnaire(QuestionnaireComposer::new(
            host_request(vec![choice_question("first"), confirm_question("second")]),
            QuestionnaireResponseChannel::new(reply_tx),
        )));
    let enter = KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE);

    assert!(app.handle_questionnaire_key(enter).unwrap());
    assert!(
        matches!(app.input_ui.composer(), ComposerMode::Questionnaire(_)),
        "enter on the first question must not submit the form"
    );
    assert!(reply_rx.try_recv().is_err());

    assert!(app.handle_questionnaire_key(enter).unwrap());
    assert!(matches!(app.input_ui.composer(), ComposerMode::Input));
    assert_eq!(
        reply_rx.try_recv(),
        Ok(QuestionnaireReply::Answer(
            HostInputResponse::new()
                .answer("first", ["alpha"])
                .answer("second", ["yes"])
        ))
    );
}

#[test]
fn resolving_questionnaire_clears_preexisting_shell_mode() {
    for submit in [true, false] {
        let (reply_tx, _reply_rx) = tokio::sync::oneshot::channel();
        let mut app = test_app();
        app.input_ui
            .set_shell_mode(Some(InlineShellMode::ExcludeFromContext));
        app.input_ui
            .set_composer(ComposerMode::Questionnaire(QuestionnaireComposer::new(
                host_request(vec![choice_question("choice")]),
                QuestionnaireResponseChannel::new(reply_tx),
            )));

        let key = if submit {
            KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)
        } else {
            KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE)
        };
        app.handle_questionnaire_key(key).unwrap();

        assert_eq!(app.input_ui.shell_mode(), None);
        assert!(app.input_ui.text().is_empty());
        assert!(matches!(app.input_ui.composer(), ComposerMode::Input));
    }
}