opaline 0.4.1

A token-based theme engine for Rust applications
Documentation
use opaline::error::OpalineError;
use opaline::loader;
use opaline::{OpalineColor, OpalineStyle};
use pretty_assertions::assert_eq;

const MINIMAL_TOML: &str = r##"
[meta]
name = "Minimal"
variant = "dark"

[palette]
red = "#ff0000"
blue = "#0000ff"

[tokens]
"accent.primary" = "red"
"accent.secondary" = "blue"

[styles]
keyword = { fg = "accent.primary", bold = true }

[gradients]
primary = ["red", "blue"]
"##;

#[test]
fn load_minimal_theme_from_string() {
    let theme = loader::load_from_str(MINIMAL_TOML, None).expect("valid TOML");
    assert_eq!(theme.meta.name, "Minimal");
    assert!(theme.is_dark());
}

#[test]
fn loaded_theme_resolves_tokens() {
    let theme = loader::load_from_str(MINIMAL_TOML, None).expect("valid TOML");
    assert_eq!(theme.color("accent.primary"), OpalineColor::new(255, 0, 0));
    assert_eq!(
        theme.color("accent.secondary"),
        OpalineColor::new(0, 0, 255)
    );
}

#[test]
fn loaded_theme_resolves_styles() {
    let theme = loader::load_from_str(MINIMAL_TOML, None).expect("valid TOML");
    let style = theme.style("keyword");
    assert_eq!(style, OpalineStyle::fg(OpalineColor::new(255, 0, 0)).bold());
}

#[test]
fn loaded_theme_resolves_gradients() {
    let theme = loader::load_from_str(MINIMAL_TOML, None).expect("valid TOML");
    assert_eq!(theme.gradient("primary", 0.0), OpalineColor::new(255, 0, 0));
    assert_eq!(theme.gradient("primary", 1.0), OpalineColor::new(0, 0, 255));
}

#[test]
fn missing_token_returns_fallback() {
    let theme = loader::load_from_str(MINIMAL_TOML, None).expect("valid TOML");
    assert_eq!(theme.color("nonexistent"), OpalineColor::FALLBACK);
}

#[test]
fn missing_style_returns_default() {
    let theme = loader::load_from_str(MINIMAL_TOML, None).expect("valid TOML");
    assert_eq!(theme.style("nonexistent"), OpalineStyle::default());
}

#[test]
fn missing_gradient_returns_fallback() {
    let theme = loader::load_from_str(MINIMAL_TOML, None).expect("valid TOML");
    assert_eq!(theme.gradient("nonexistent", 0.5), OpalineColor::FALLBACK);
}

#[test]
fn has_token_checks() {
    let theme = loader::load_from_str(MINIMAL_TOML, None).expect("valid TOML");
    assert!(theme.has_token("accent.primary"));
    assert!(!theme.has_token("nonexistent"));
}

#[test]
fn has_style_checks() {
    let theme = loader::load_from_str(MINIMAL_TOML, None).expect("valid TOML");
    assert!(theme.has_style("keyword"));
    assert!(!theme.has_style("nonexistent"));
}

#[test]
fn has_gradient_checks() {
    let theme = loader::load_from_str(MINIMAL_TOML, None).expect("valid TOML");
    assert!(theme.has_gradient("primary"));
    assert!(!theme.has_gradient("nonexistent"));
}

#[test]
fn invalid_toml_returns_parse_error() {
    let err = loader::load_from_str("this is not toml {{{", None).expect_err("should fail");
    assert!(matches!(err, OpalineError::Parse { .. }));
}

#[test]
fn load_from_file_nonexistent_returns_io_error() {
    let err = loader::load_from_file(std::path::Path::new("/tmp/opaline_nonexistent.toml"))
        .expect_err("should fail");
    assert!(matches!(err, OpalineError::Io { .. }));
}

#[test]
fn theme_token_names() {
    let theme = loader::load_from_str(MINIMAL_TOML, None).expect("valid TOML");
    let names = theme.token_names();
    assert!(names.contains(&"accent.primary"));
    assert!(names.contains(&"accent.secondary"));
}

#[test]
fn theme_style_names() {
    let theme = loader::load_from_str(MINIMAL_TOML, None).expect("valid TOML");
    let names = theme.style_names();
    assert!(names.contains(&"keyword"));
}

#[test]
fn theme_gradient_names() {
    let theme = loader::load_from_str(MINIMAL_TOML, None).expect("valid TOML");
    let names = theme.gradient_names();
    assert!(names.contains(&"primary"));
}

#[test]
fn light_theme_variant() {
    let toml = r#"
[meta]
name = "Light Test"
variant = "light"
"#;
    let theme = loader::load_from_str(toml, None).expect("valid TOML");
    assert!(theme.is_light());
    assert!(!theme.is_dark());
}

#[test]
fn empty_gradient_array_returns_error() {
    let toml = r#"
[meta]
name = "Empty Gradient"

[gradients]
primary = []
"#;

    let err = loader::load_from_str(toml, None).expect_err("should fail");
    assert!(matches!(err, OpalineError::EmptyGradient));
}

#[test]
fn unknown_top_level_field_is_rejected() {
    let toml = r#"
oops = "nope"

[meta]
name = "Unknown Top Level"
"#;

    let err = loader::load_from_str(toml, None).expect_err("should fail");
    assert!(matches!(err, OpalineError::Parse { .. }));
}

#[test]
fn unknown_meta_field_is_rejected() {
    let toml = r#"
[meta]
name = "Unknown Meta"
variant = "dark"
unexpected = "nope"
"#;

    let err = loader::load_from_str(toml, None).expect_err("should fail");
    assert!(matches!(err, OpalineError::Parse { .. }));
}

#[test]
fn unknown_style_field_is_rejected() {
    let toml = r#"
[meta]
name = "Unknown Style"

[styles]
keyword = { fg = "accent.primary", bold = true, unexpected = true }
"#;

    let err = loader::load_from_str(toml, None).expect_err("should fail");
    assert!(matches!(err, OpalineError::Parse { .. }));
}