acta 0.0.1

A customizable logging library for Rust
Documentation
use super::*;
use crate::config::LogRotation;
use crate::rotate_log_file;
use smallvec::SmallVec;

#[test]
fn ansi_formatter_defaults() {
    let fmt = AnsiFormatter::new();
    assert_eq!(fmt.time_format, "%H:%M:%S");
    assert_eq!(fmt.path_width, BUILD_PATH_WIDTH);
    assert!(fmt.show_path);
    assert!(fmt.show_spans);
}

#[test]
fn ansi_formatter_builder() {
    let fmt = AnsiFormatter::new()
        .with_time_format("%Y-%m-%d %H:%M:%S")
        .with_path_width(40)
        .with_show_path(false)
        .with_show_spans(false)
        .with_theme(Theme::monokai());

    assert_eq!(fmt.time_format, "%Y-%m-%d %H:%M:%S");
    assert_eq!(fmt.path_width, 40);
    assert!(!fmt.show_path);
    assert!(!fmt.show_spans);
}

#[cfg(feature = "nerd")]
#[test]
fn ansi_formatter_with_icons() {
    let fmt = AnsiFormatter::new().with_icons(Icons::nerd());
    assert_eq!(fmt.style_config().icons.bracket_open, "\u{e0b6}");
}

#[test]
fn theme_presets_are_distinct() {
    let s1 = format!("{:?}", Theme::trans_flag());
    let s2 = format!("{:?}", Theme::monokai());
    let s3 = format!("{:?}", Theme::dracula());
    assert_ne!(s1, s2);
    assert_ne!(s2, s3);
}

#[test]
fn theme_default_is_trans_flag() {
    assert_eq!(
        format!("{:?}", Theme::default()),
        format!("{:?}", Theme::trans_flag())
    );
}

#[cfg(feature = "nerd")]
#[test]
fn icons_unicode_vs_nerd() {
    let u = Icons::unicode();
    let n = Icons::nerd();
    assert_ne!(u.bracket_open, n.bracket_open);
    assert_ne!(u.bracket_close, n.bracket_close);
    assert_ne!(u.arrow, n.arrow);
    assert_eq!(u.separator, n.separator);
}

#[test]
fn smart_truncate_short_path() {
    let result = AnsiFormatter::smart_truncate("foo.rs", 10, 20);
    assert_eq!(result.len(), 20);
    assert!(result.contains("foo.rs:10"));
}

#[test]
fn smart_truncate_exact_width() {
    let result = AnsiFormatter::smart_truncate("foo.rs", 1, 8);
    assert_eq!(result.as_str(), "foo.rs:1");
}

#[test]
fn smart_truncate_overflow() {
    let result = AnsiFormatter::smart_truncate("very/long/path/file.rs", 999, 15);
    assert!(result.len() <= 15);
}

#[test]
fn smart_truncate_trailing_slash_before_filename() {
    let result = AnsiFormatter::smart_truncate("dir/subdir/file.rs", 42, 20);
    assert!(result.len() <= 20);
    assert!(result.contains("file.rs:42"));
}

fn smart_truncate_file_part_too_long() {
    let result = AnsiFormatter::smart_truncate("very_very_long_filename_test.rs", 10, 15);
    assert!(
        result.len() <= 18,
        "result='{}', len={}",
        result,
        result.len()
    );
    assert!(result.starts_with(''));
}

#[test]
fn format_path_strips_src() {
    let result = AnsiFormatter::format_path("C:\\project\\src\\lib.rs", 42, 20);
    assert!(result.contains("lib.rs:42"));
    assert!(!result.contains("src/"));
}

#[test]
fn rotate_nonexistent_file_is_noop() {
    let path = std::env::temp_dir().join("acta-test-nonexistent.log");
    drop(std::fs::remove_file(&path));
    assert!(rotate_log_file(&path, LogRotation::Rename).is_ok());
    #[cfg(feature = "compress")]
    assert!(rotate_log_file(&path, LogRotation::Compress).is_ok());
    assert!(rotate_log_file(&path, LogRotation::None).is_ok());
}

#[test]
fn rotate_none_keeps_file() {
    let dir = std::env::temp_dir().join("acta-test-fmt-none");
    drop(std::fs::create_dir_all(&dir));
    let path = dir.join("app.log");
    std::fs::write(&path, b"hello\n").unwrap();

    rotate_log_file(&path, LogRotation::None).unwrap();
    assert!(path.exists());
    assert_eq!(std::fs::read_to_string(&path).unwrap(), "hello\n");
    drop(std::fs::remove_dir_all(&dir));
}

#[test]
fn rotate_rename() {
    let dir = std::env::temp_dir().join("acta-test-fmt-rename");
    drop(std::fs::remove_dir_all(&dir));
    drop(std::fs::create_dir_all(&dir));
    let path = dir.join("app.log");
    std::fs::write(&path, b"old content\n").unwrap();

    rotate_log_file(&path, LogRotation::Rename).unwrap();
    assert!(!path.exists());

    let entries: Vec<_> = std::fs::read_dir(&dir).unwrap().flatten().collect();
    assert_eq!(entries.len(), 1);
    let content = std::fs::read_to_string(entries[0].path()).unwrap();
    assert_eq!(content, "old content\n");
    drop(std::fs::remove_dir_all(&dir));
}

#[test]
#[cfg(feature = "compress")]
fn rotate_compress() {
    let dir = std::env::temp_dir().join("acta-test-fmt-compress");
    drop(std::fs::remove_dir_all(&dir));
    drop(std::fs::create_dir_all(&dir));
    let path = dir.join("app.log");
    std::fs::write(&path, b"compress me\n").unwrap();

    rotate_log_file(&path, LogRotation::Compress).unwrap();
    assert!(!path.exists());

    let entries: Vec<_> = std::fs::read_dir(&dir).unwrap().flatten().collect();
    assert_eq!(entries.len(), 1);
    let gz_data = std::fs::read(entries[0].path()).unwrap();
    assert!(gz_data.len() > 2);
    assert_eq!(gz_data[0], 0x1f);
    assert_eq!(gz_data[1], 0x8b);
    drop(std::fs::remove_dir_all(&dir));
}

#[test]
fn ansi_formatter_style_config_returns_reference() {
    let fmt = AnsiFormatter::new();
    let config = fmt.style_config();
    assert_eq!(config.labels.error, "E");
}

#[test]
fn ansi_formatter_with_style_config_replaces_all() {
    let config = StyleConfig {
        labels: LevelLabels::short(),
        icons: Icons::unicode(),
        theme: Theme::monokai(),
    };
    let fmt = AnsiFormatter::new().with_style_config(config);
    assert_eq!(fmt.style_config().labels, LevelLabels::short());
    assert_eq!(fmt.style_config().icons.bracket_open, "[");
    assert!((248..=255).contains(&fmt.style_config().theme.error.rgb.0));
}

#[test]
fn ansi_formatter_with_labels_changes_labels() {
    let fmt = AnsiFormatter::new();
    let before = fmt.style_config().labels.error;

    let fmt = fmt.with_labels(LevelLabels::long());
    assert_ne!(fmt.style_config().labels.error, before);
    assert_eq!(fmt.style_config().labels.error, "ERROR");
}

#[test]
fn ansi_formatter_with_icons_changes_icons() {
    let fmt = AnsiFormatter::new().with_icons(Icons::unicode());
    assert_eq!(fmt.style_config().icons.bracket_open, "[");
}

#[test]
fn ansi_formatter_with_theme_changes_theme() {
    let fmt = AnsiFormatter::new().with_theme(Theme::monokai());
    assert_ne!(
        format!("{:?}", fmt.style_config().theme),
        format!("{:?}", Theme::default())
    );
}

#[test]
fn event_visitor_records_message_field() {
    let mut visitor = EventVisitor::default();
    visitor.record_field("message", "hello".to_owned());
    assert_eq!(visitor.message, Some("hello".to_owned()));
    assert!(visitor.fields.is_empty());
}

#[test]
fn event_visitor_records_msg_alias() {
    let mut visitor = EventVisitor::default();
    visitor.record_field("msg", "world".to_owned());
    assert_eq!(visitor.message, Some("world".to_owned()));
    assert!(visitor.fields.is_empty());
}

#[test]
fn event_visitor_records_other_fields_as_pairs() {
    let mut visitor = EventVisitor::default();
    visitor.record_field("user", "alice".to_owned());
    visitor.record_field("count", "42".to_owned());
    assert!(visitor.message.is_none());
    assert_eq!(
        visitor.fields,
        SmallVec::<[(String, String); 4]>::from_vec(vec![
            ("user".to_owned(), "alice".to_owned()),
            ("count".to_owned(), "42".to_owned())
        ])
    );
}

#[test]
fn event_visitor_default_has_no_message_and_empty_fields() {
    let visitor = EventVisitor::default();
    assert!(visitor.message.is_none());
    assert!(visitor.fields.is_empty());
}

#[test]
fn event_visitor_order_preserved_message_extracted() {
    let mut visitor = EventVisitor::default();
    visitor.record_field("x", "1".to_owned());
    visitor.record_field("message", "the message".to_owned());
    visitor.record_field("y", "2".to_owned());
    assert_eq!(visitor.message, Some("the message".to_owned()));
    assert_eq!(
        visitor.fields,
        SmallVec::<[(String, String); 4]>::from_vec(vec![
            ("x".to_owned(), "1".to_owned()),
            ("y".to_owned(), "2".to_owned())
        ])
    );
}

#[test]
fn level_labels_short() {
    let labels = LevelLabels::short();
    assert_eq!(labels.error, "E");
    assert_eq!(labels.warn, "W");
    assert_eq!(labels.info, "I");
    assert_eq!(labels.debug, "D");
    assert_eq!(labels.trace, "T");
}

#[test]
fn level_labels_long() {
    let labels = LevelLabels::long();
    assert_eq!(labels.error, "ERROR");
    assert_eq!(labels.warn, " WARN");
    assert_eq!(labels.info, " INFO");
    assert_eq!(labels.debug, "DEBUG");
    assert_eq!(labels.trace, "TRACE");
}

#[test]
fn level_labels_default_equals_short() {
    assert_eq!(LevelLabels::default(), LevelLabels::short());
}

#[test]
fn icons_unicode() {
    let icons = Icons::unicode();
    assert_eq!(icons.bracket_open, "[");
    assert_eq!(icons.bracket_close, "]");
    assert_eq!(icons.separator, "");
    assert_eq!(icons.arrow, "");
    assert_eq!(icons.span_delimiter, "->");
}

#[test]
fn icons_is_nerd_returns_false_for_unicode() {
    let icons = Icons::unicode();
    assert!(!icons.is_nerd());
}

#[cfg(feature = "nerd")]
#[test]
fn icons_is_nerd_returns_true_for_nerd() {
    let icons = Icons::nerd();
    assert!(icons.is_nerd());
}

#[test]
fn style_config_default() {
    let config = StyleConfig::default();
    assert_eq!(
        format!("{:?}", config.theme),
        format!("{:?}", Theme::trans_flag())
    );
    #[cfg(feature = "nerd")]
    assert_eq!(config.icons, Icons::nerd());
    #[cfg(not(feature = "nerd"))]
    assert_eq!(config.icons, Icons::unicode());
    assert_eq!(config.labels, LevelLabels::short());
}

#[test]
fn theme_all_have_distinct_accent_colors() {
    let themes = [
        Theme::trans_flag(),
        Theme::monokai(),
        Theme::dracula(),
        Theme::nord(),
        Theme::catppuccin_mocha(),
        Theme::gruvbox(),
        Theme::one_dark(),
        Theme::tokyo_night(),
    ];
    for i in 0..themes.len() {
        for j in (i + 1)..themes.len() {
            assert_ne!(
                format!("{:?}", themes[i].accent),
                format!("{:?}", themes[j].accent),
                "Theme {} and {} should have distinct accent colors",
                i,
                j
            );
        }
    }
}

#[test]
fn theme_default_equals_trans_flag() {
    assert_eq!(
        format!("{:?}", Theme::default()),
        format!("{:?}", Theme::trans_flag())
    );
}