standout 9.0.0

Styled CLI template rendering with automatic terminal detection
use clap::Command;
use console::Style;
use serde_json::json;
use serial_test::serial;
use standout::cli::FnHandler;
use standout::cli::{App, Output};
use standout::views::list_view;
use standout::EmbeddedTemplates;
use standout::{OutputMode, Theme};
use standout_test::TestHarness;

const TEMPLATES: &[(&str, &str)] = &[("say", "[missing_style]hello[/missing_style]")];

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

    let result = TestHarness::new().output_mode(OutputMode::Term).run(
        &app,
        Command::new("app").subcommand(Command::new("say")),
        ["app", "say"],
    );

    result.assert_success();
    assert_eq!(result.stdout(), "hello");
    assert!(!result.stdout().contains("?]"));
    assert_eq!(
        result.warnings(),
        ["Unresolved style tag(s) degraded to unstyled text: missing_style"]
    );
}

#[test]
fn framework_templates_fail_build_when_the_resolved_theme_lacks_their_tags() {
    let result = App::builder()
        .templates(EmbeddedTemplates::new(TEMPLATES, ""))
        .include_framework_styles(false)
        .build();

    let err = match result {
        Ok(_) => panic!("build should reject framework templates without framework styles"),
        Err(err) => err.to_string(),
    };

    assert!(err.contains("framework template"), "{err}");
    assert!(err.contains("standout-muted"), "{err}");
    assert!(err.contains(".include_framework_styles(true)"), "{err}");
    assert!(err.contains(".theme(...)"), "{err}");
    assert!(err.contains(".include_framework_templates(false)"), "{err}");
}

#[test]
fn missing_default_theme_names_builder_calls_that_supply_it() {
    let err = match App::builder()
        .templates(EmbeddedTemplates::new(TEMPLATES, ""))
        .default_theme("missing")
        .build()
    {
        Ok(_) => panic!("default_theme without configured styles must fail"),
        Err(error) => error.to_string(),
    };

    assert!(err.contains("theme `missing` not found"), "{err}");
    assert!(err.contains(".styles(embed_styles!"), "{err}");
    assert!(err.contains(".styles_dir(\"path/to/styles\")"), "{err}");
    assert!(err.contains(".default_theme(...)"), "{err}");
    assert!(err.contains(".theme(...)"), "{err}");
}

#[test]
#[serial]
fn app_theme_overlays_framework_styles_per_tag() {
    let app = App::builder()
        .templates(EmbeddedTemplates::new(TEMPLATES, ""))
        .theme(Theme::new().add("standout-muted", Style::new().red().force_styling(true)))
        .command_with(
            "list",
            FnHandler::new(|_m, _ctx| {
                Ok(Output::Render(
                    list_view(vec!["one"])
                        .total_count(3)
                        .filter_summary("status=pending")
                        .build(),
                ))
            }),
            |config| config.template_name("standout/list-view"),
        )
        .unwrap()
        .build()
        .unwrap();

    let result = TestHarness::new().output_mode(OutputMode::Term).run(
        &app,
        Command::new("app").subcommand(Command::new("list")),
        ["app", "list"],
    );

    result.assert_success();
    assert!(
        result.stdout().contains("\x1b[31m"),
        "app override for framework tag should win:\n{}",
        result.stdout()
    );
    assert!(!result.stdout().contains("?]"));
}