standout 9.0.0

Styled CLI template rendering with automatic terminal detection
use clap::Command;
use serde_json::json;
use standout::cli::FnHandler;
use standout::cli::{App, HandlerResult, Output};
use standout::AmbiguousWidth;
use standout::EmbeddedTemplates;
use std::cell::RefCell;
use std::rc::Rc;

const TEMPLATES: &[(&str, &str)] = &[
    ("test", "{{ msg }}"),
    ("width", "{{ indicator | display_width }}"),
    ("inc", "{{ count }}"),
    ("add", "{{ val }}"),
];

#[test]
fn test_app_integration() {
    let app = App::builder()
        .templates(EmbeddedTemplates::new(TEMPLATES, ""))
        .command_with(
            "test",
            FnHandler::new(|_m, _ctx| Ok(Output::Render(json!({"msg": "success"})))),
            |cfg| cfg,
        )
        .unwrap()
        .build()
        .unwrap();

    let cmd = Command::new("test").subcommand(Command::new("test"));
    let result = app.run_with(
        cmd,
        vec!["test", "test"],
        standout::TargetProperties::detect(),
        standout::InputSources::from_process(),
    );
    if let standout::cli::DispatchResult::Handled(output) = result.outcome() {
        assert_eq!(output, "success");
    } else {
        panic!("Expected DispatchResult::Handled, got {:?}", result);
    }
}

#[test]
fn app_builder_wide_policy_reaches_dispatch_rendering() {
    let app = App::builder()
        .templates(EmbeddedTemplates::new(TEMPLATES, ""))
        .ambiguous_width(AmbiguousWidth::Wide)
        .command_with(
            "width",
            FnHandler::new(|_m, _ctx| Ok(Output::Render(json!({"indicator": "↦≈Δ"})))),
            |cfg| cfg,
        )
        .unwrap()
        .build()
        .unwrap();

    let cmd = Command::new("test").subcommand(Command::new("width"));
    let matches = cmd.try_get_matches_from(["test", "width"]).unwrap();
    let result = app.dispatch(matches, standout::OutputMode::Text);
    assert_eq!(result.output(), Some("5"));
}

#[test]
fn test_app_with_mutable_state() {
    let counter = Rc::new(RefCell::new(0));
    let counter_clone = counter.clone();

    let app = App::builder()
        .templates(EmbeddedTemplates::new(TEMPLATES, ""))
        .command_with(
            "inc",
            FnHandler::new(move |_m, _ctx| {
                *counter_clone.borrow_mut() += 1;
                Ok(Output::Render(json!({"count": *counter_clone.borrow()})))
            }),
            |cfg| cfg,
        )
        .unwrap()
        .build()
        .unwrap();

    let cmd = Command::new("test").subcommand(Command::new("inc"));
    let result = app.run_with(
        cmd,
        vec!["test", "inc"],
        standout::TargetProperties::detect(),
        standout::InputSources::from_process(),
    );

    if let standout::cli::DispatchResult::Handled(output) = result.outcome() {
        assert_eq!(output, "1");
    } else {
        panic!("Expected DispatchResult::Handled, got {:?}", result);
    }
    assert_eq!(*counter.borrow(), 1);
}

#[test]
fn test_struct_handler_with_state() {
    struct StatefulHandler {
        count: i32,
    }

    impl standout::cli::Handler for StatefulHandler {
        type Output = serde_json::Value;

        fn handle(
            &mut self,
            _m: &clap::ArgMatches,
            _ctx: &standout::cli::CommandContext,
        ) -> HandlerResult<serde_json::Value> {
            self.count += 10;
            Ok(Output::Render(json!({"val": self.count})))
        }
    }

    let app = App::builder()
        .templates(EmbeddedTemplates::new(TEMPLATES, ""))
        .command_with("add", StatefulHandler { count: 0 }, |cfg| cfg)
        .unwrap()
        .build()
        .unwrap();

    let cmd = Command::new("test").subcommand(Command::new("add"));
    let result1 = app.run_with(
        cmd.clone(),
        vec!["test", "add"],
        standout::TargetProperties::detect(),
        standout::InputSources::from_process(),
    );
    if let standout::cli::DispatchResult::Handled(output) = result1.outcome() {
        assert_eq!(output, "10");
    } else {
        panic!("Expected DispatchResult::Handled, got {:?}", result1);
    }

    let result2 = app.run_with(
        cmd,
        vec!["test", "add"],
        standout::TargetProperties::detect(),
        standout::InputSources::from_process(),
    );
    if let standout::cli::DispatchResult::Handled(output) = result2.outcome() {
        assert_eq!(output, "20");
    } else {
        panic!("Expected DispatchResult::Handled, got {:?}", result2);
    }
}