use super::is_listed;
use super::spend_format::{self, aic_to_usd, nano_to_aic};
use crate::config::CostConfig;
use crate::theme::Palette;
pub const NAME: &str = "session_cost";
pub fn needs_spike_check(segments: &[String], config: &CostConfig) -> bool {
config.spike_alert && is_listed(segments, NAME)
}
pub fn render_session_cost_segment(
total_nano_aiu: u64,
spike: bool,
config: &CostConfig,
icon_set: crate::config::IconSet,
palette: &Palette,
) -> Option<String> {
if !config.enabled {
return None;
}
let r = palette.reset;
let d = palette.dim;
let lbl = palette.label;
let aic = nano_to_aic(total_nano_aiu);
let usd = aic_to_usd(aic);
let over_limit = spend_format::exceeds(usd, config.decimal_places, config.alert_above_usd);
let limit_icon = if over_limit {
config.alert_icon.as_str()
} else {
""
};
let spike_icon = if spike {
config.spike_icon.as_str()
} else {
""
};
let color = if over_limit || spike {
palette.tokens_alert
} else {
palette.spend
};
let cost_str = format!(
"{}{}{:.*}{}",
color, config.currency_symbol, config.decimal_places, usd, r
);
let icon = crate::icons::session_icon(icon_set, config.prefix.as_deref());
let mut out = format!(
"{}{}{} {}{}{}",
lbl, icon, r, limit_icon, spike_icon, cost_str
);
if config.show_aic && aic >= 0.1 {
out.push_str(&format!(" ({}{:.1} AIC{})", d, aic, r));
}
Some(out)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::IconSet;
use crate::segments::names;
#[test]
fn test_zero_spend_no_aic() {
let cfg = CostConfig::default(); let p = Palette::for_theme("plain");
let rendered = render_session_cost_segment(0, false, &cfg, IconSet::Plain, &p).unwrap();
assert_eq!(rendered, "Session: $0.00");
}
#[test]
fn test_spend_with_emoji() {
let cfg = CostConfig::default();
let p = Palette::for_theme("plain");
let rendered = render_session_cost_segment(0, false, &cfg, IconSet::Emoji, &p).unwrap();
assert_eq!(rendered, "💰 $0.00");
}
#[test]
fn test_spend_with_show_aic_enabled() {
let cfg = CostConfig {
show_aic: true,
..Default::default()
};
let p = Palette::for_theme("plain");
let rendered =
render_session_cost_segment(500_000_000_000, false, &cfg, IconSet::Plain, &p).unwrap();
assert_eq!(rendered, "Session: $5.00 (500.0 AIC)");
}
#[test]
fn test_spend_with_show_aic_disabled() {
let cfg = CostConfig {
show_aic: false,
..Default::default()
};
let p = Palette::for_theme("plain");
let rendered =
render_session_cost_segment(500_000_000_000, false, &cfg, IconSet::Plain, &p).unwrap();
assert_eq!(rendered, "Session: $5.00");
}
#[test]
fn test_spend_spike_is_flagged() {
let cfg = CostConfig::default();
let p = Palette::for_theme("plain");
let rendered =
render_session_cost_segment(80_000_000_000, true, &cfg, IconSet::Plain, &p).unwrap();
assert_eq!(rendered, "Session: 📈 $0.80");
}
#[test]
fn test_spend_spike_uses_alert_color() {
let cfg = CostConfig::default();
let p = Palette::for_theme("github");
let rendered =
render_session_cost_segment(80_000_000_000, true, &cfg, IconSet::Plain, &p).unwrap();
assert!(rendered.contains(&format!("{}$0.80{}", p.tokens_alert, p.reset)));
}
fn with_limit(usd: f64) -> CostConfig {
CostConfig {
alert_above_usd: Some(usd),
..Default::default()
}
}
#[test]
fn test_spend_under_limit_is_not_flagged() {
let p = Palette::for_theme("plain");
let rendered = render_session_cost_segment(
400_000_000_000,
false,
&with_limit(5.0),
IconSet::Plain,
&p,
)
.unwrap();
assert_eq!(rendered, "Session: $4.00");
}
#[test]
fn test_spend_exactly_at_limit_is_not_flagged() {
let p = Palette::for_theme("plain");
let rendered = render_session_cost_segment(
500_000_000_000,
false,
&with_limit(5.0),
IconSet::Plain,
&p,
)
.unwrap();
assert_eq!(rendered, "Session: $5.00");
}
#[test]
fn test_spend_at_limit_ignores_float_error() {
let p = Palette::for_theme("plain");
let rendered = render_session_cost_segment(
57_000_000_000,
false,
&with_limit(0.57),
IconSet::Plain,
&p,
)
.unwrap();
assert_eq!(rendered, "Session: $0.57");
}
#[test]
fn test_spend_over_limit_only_once_the_shown_amount_is() {
let p = Palette::for_theme("plain");
let rendered = render_session_cost_segment(
500_400_000_000,
false,
&with_limit(5.0),
IconSet::Plain,
&p,
)
.unwrap();
assert_eq!(rendered, "Session: $5.00");
}
#[test]
fn test_spend_over_limit_is_flagged() {
let p = Palette::for_theme("plain");
let rendered = render_session_cost_segment(
620_000_000_000,
false,
&with_limit(5.0),
IconSet::Plain,
&p,
)
.unwrap();
assert_eq!(rendered, "Session: 💸 $6.20");
}
#[test]
fn test_spend_over_limit_uses_alert_color() {
let p = Palette::for_theme("github");
let rendered = render_session_cost_segment(
620_000_000_000,
false,
&with_limit(5.0),
IconSet::Plain,
&p,
)
.unwrap();
assert!(rendered.contains(&format!("{}$6.20{}", p.tokens_alert, p.reset)));
}
#[test]
fn test_spend_without_limit_is_never_flagged() {
let cfg = CostConfig::default(); let p = Palette::for_theme("plain");
let rendered =
render_session_cost_segment(620_000_000_000_000, false, &cfg, IconSet::Plain, &p)
.unwrap();
assert_eq!(rendered, "Session: $6200.00");
}
#[test]
fn test_spike_over_limit_shows_both_icons() {
let p = Palette::for_theme("plain");
let rendered = render_session_cost_segment(
620_000_000_000,
true,
&with_limit(5.0),
IconSet::Plain,
&p,
)
.unwrap();
assert_eq!(rendered, "Session: 💸 📈 $6.20");
}
#[test]
fn test_spike_check_needs_opt_in_and_a_listed_segment() {
let on = CostConfig {
spike_alert: true,
..Default::default()
};
assert!(needs_spike_check(&names(&["session_cost"]), &on));
assert!(!needs_spike_check(&names(&["tokens"]), &on));
assert!(!needs_spike_check(
&names(&["session_cost"]),
&CostConfig::default()
));
}
#[test]
fn test_spike_check_ignores_enabled() {
let cfg = CostConfig {
enabled: false,
spike_alert: true,
..Default::default()
};
assert!(needs_spike_check(&names(&["session_cost"]), &cfg));
}
}