clauth 0.5.6

Simple Claude Code account switcher and usage monitor
use super::*;
use crate::profile::{AppConfig, AppState};
use crate::tui::app::App;
use ratatui::Terminal;
use ratatui::backend::TestBackend;

fn truncate_label(label: String, max_chars: usize) -> String {
    let char_count = label.chars().count();
    if char_count <= max_chars {
        return label;
    }
    if max_chars == 0 {
        return String::new();
    }
    if let Some(suffix_start) = label.rfind('(') {
        let suffix = &label[suffix_start..];
        let body = &label[..suffix_start];
        let suffix_chars = suffix.chars().count();
        let body_budget = max_chars.saturating_sub(suffix_chars + 1);
        if body_budget > 0 {
            let trimmed_body: String = body.chars().take(body_budget).collect();
            return format!("{trimmed_body}{suffix}");
        }
    }
    let trimmed: String = label.chars().take(max_chars.saturating_sub(1)).collect();
    format!("{trimmed}")
}

fn empty_app(active: Tab) -> App {
    let mut app = App::new(AppConfig {
        state: AppState::default(),
        profiles: Vec::new(),
    });
    app.tab = active;
    app
}

fn render_tabs(app: &App, width: u16) -> String {
    let mut term = Terminal::new(TestBackend::new(width, 1)).unwrap();
    term.draw(|f| {
        let area = f.area();
        super::draw(f, area, app);
    })
    .unwrap();
    let buf = term.backend().buffer().clone();
    (0..width)
        .map(|x| buf.content[x as usize].symbol().to_owned())
        .collect()
}

#[test]
fn normal_form_shows_all_tabs() {
    let app = empty_app(Tab::Overview);
    let s = render_tabs(&app, 70);
    assert!(s.contains("overview"), "active tab missing");
    assert!(s.contains("usage"), "inactive tab missing");
    assert!(s.contains("setup"), "inactive tab missing");
    assert!(s.contains("fallback"), "inactive tab missing");
    assert!(s.contains("config"), "inactive tab missing");
    assert!(s.contains("status"), "inactive tab missing");
}

#[test]
fn normal_form_labels_untruncated_at_tight_boundary() {
    let app = empty_app(Tab::Overview);
    let s = render_tabs(&app, 53);
    assert!(
        s.contains("overview"),
        "overview must not be truncated at tight boundary"
    );
    assert!(
        s.contains("status"),
        "status must not be truncated at tight boundary"
    );
}

#[test]
fn overflow_form_shows_only_active() {
    let app = empty_app(Tab::Usage);
    let s = render_tabs(&app, 10);
    assert!(
        s.contains("usage"),
        "active label must appear in overflow form"
    );
    assert!(
        !s.contains("overview"),
        "non-active tab must not appear in overflow"
    );
    assert!(
        !s.contains("config"),
        "non-active tab must not appear in overflow"
    );
}

#[test]
fn overflow_chevrons_at_middle_tab() {
    let app = empty_app(Tab::Usage);
    let s = render_tabs(&app, 15);
    assert!(
        s.contains(''),
        "left chevron must appear for non-first tab"
    );
    assert!(
        s.contains(''),
        "right chevron must appear for non-last tab"
    );
}

#[test]
fn overflow_no_left_chevron_at_first_tab() {
    let app = empty_app(Tab::Overview);
    let s = render_tabs(&app, 12);
    assert!(!s.contains(''), "no left chevron at first tab");
    assert!(
        s.contains(''),
        "right chevron must appear when more tabs follow"
    );
}

#[test]
fn overflow_no_right_chevron_at_last_tab() {
    let app = empty_app(Tab::Status);
    let s = render_tabs(&app, 12);
    assert!(
        s.contains(''),
        "left chevron must appear for non-first tab"
    );
    assert!(!s.contains(''), "no right chevron at last tab");
}

#[test]
fn truncate_label_plain() {
    assert_eq!(truncate_label("overview".into(), 5), "over…");
    assert_eq!(truncate_label("overview".into(), 8), "overview");
    assert_eq!(truncate_label("overview".into(), 0), "");
}

#[test]
fn truncate_label_preserves_suffix() {
    assert_eq!(truncate_label("my-long-name(45%)".into(), 10), "my-l…(45%)");
    assert_eq!(truncate_label("done(✓)".into(), 6), "do…(✓)");
}