standout 8.1.1

Styled CLI template rendering with automatic terminal detection
//! `App::builder().version(...)` as seen from every parse path.
//!
//! The builder setting only supplies the value; clap keeps answering
//! `--version` with it, so what these assert is where the value reaches and
//! that leaving it unset changes nothing.

use clap::Command;
use serde_json::json;
use standout::cli::{App, ExitStatus, HelpResult, Output, RunErrorKind, SuccessKind};

fn versionless_command() -> Command {
    Command::new("app").subcommand(Command::new("go"))
}

fn app_with(version: Option<&str>) -> App {
    let builder = App::builder()
        .command(
            "go",
            |_matches, _ctx| Ok(Output::Render(json!({ "message": "ok" }))),
            "{{ message }}",
        )
        .unwrap();
    match version {
        Some(version) => builder.version(version),
        None => builder,
    }
    .build()
    .unwrap()
}

fn app_with_owned_version(version: String) -> App {
    App::builder()
        .command(
            "go",
            |_matches, _ctx| Ok(Output::Render(json!({ "message": "ok" }))),
            "{{ message }}",
        )
        .unwrap()
        .version(version)
        .build()
        .unwrap()
}

#[test]
fn a_configured_version_is_answered_as_a_clap_display_success() {
    let result = app_with(Some("9.9.9")).run_to_string(versionless_command(), ["app", "--version"]);

    assert_eq!(result.exit_status(), Some(ExitStatus::SUCCESS));
    assert_eq!(result.success_kind(), Some(SuccessKind::ClapVersion));
    assert_eq!(result.output().unwrap().trim(), "app 9.9.9");
}

#[test]
fn an_owned_string_version_is_accepted() {
    let result = app_with_owned_version(String::from("8.8.8"))
        .run_to_string(versionless_command(), ["app", "--version"]);

    assert_eq!(result.exit_status(), Some(ExitStatus::SUCCESS));
    assert_eq!(result.success_kind(), Some(SuccessKind::ClapVersion));
    assert_eq!(result.output().unwrap().trim(), "app 8.8.8");
}

#[test]
fn the_parse_only_path_sees_the_same_version() {
    // `get_matches_from` speaks `clap::Error` for everything clap displays,
    // `--version` included; what matters is that the value arrived.
    let result =
        app_with(Some("9.9.9")).get_matches_from(versionless_command(), ["app", "--version"]);

    match result {
        HelpResult::Error(error) => {
            assert_eq!(error.kind(), clap::error::ErrorKind::DisplayVersion);
            assert!(
                error.to_string().contains("9.9.9"),
                "unexpected version display: {error}"
            );
        }
        other => panic!("expected clap's version display, got {other:?}"),
    }
}

#[test]
fn an_unset_version_leaves_a_version_configured_on_the_command() {
    let command = versionless_command().version("1.2.3");
    let result = app_with(None).run_to_string(command, ["app", "--version"]);

    assert_eq!(result.success_kind(), Some(SuccessKind::ClapVersion));
    assert_eq!(result.output().unwrap().trim(), "app 1.2.3");
}

#[test]
fn an_unset_version_leaves_a_versionless_command_versionless() {
    let result = app_with(None).run_to_string(versionless_command(), ["app", "--version"]);

    assert_eq!(result.error_kind(), Some(RunErrorKind::ClapUsage));
    assert!(
        result.error().unwrap().contains("unexpected argument"),
        "a versionless command has no --version: {}",
        result.error().unwrap()
    );
}

#[test]
fn a_configured_version_wins_over_one_set_on_the_command() {
    // The application said it twice; the builder is the later word, and the
    // one standout can see.
    let command = versionless_command().version("1.2.3");
    let result = app_with(Some("9.9.9")).run_to_string(command, ["app", "--version"]);

    assert_eq!(result.output().unwrap().trim(), "app 9.9.9");
}

#[test]
fn a_configured_version_leaves_dispatch_alone() {
    let result = app_with(Some("9.9.9")).run_to_string(versionless_command(), ["app", "go"]);

    assert_eq!(result.exit_status(), Some(ExitStatus::SUCCESS));
    assert_eq!(result.output(), Some("ok"));
}