#![allow(dead_code)]
use super::super::theme::Theme;
use ratatui::{
style::{Modifier, Style},
text::{Line, Span},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TraceState {
Pending,
Running,
Done,
Error,
}
pub fn trace_line(state: TraceState, text: &str, expandable: bool, theme: &Theme) -> Line<'static> {
let dot_color = match state {
TraceState::Pending => theme.text_muted,
TraceState::Running => theme.warning,
TraceState::Done => theme.success,
TraceState::Error => theme.error,
};
let mut spans = vec![
Span::styled("● ", Style::default().fg(dot_color)),
Span::styled(text.to_owned(), Style::default().fg(theme.text_strong)),
];
if expandable {
spans.push(Span::styled(
" (ctrl+o expandir)".to_owned(),
Style::default().fg(theme.text_muted),
));
}
Line::from(spans)
}
pub fn thought_block(
seconds: u64,
tokens: usize,
summary: &str,
theme: &Theme,
) -> Vec<Line<'static>> {
let muted = Style::default().fg(theme.text_muted);
let header = Line::from(Span::styled(
format!("▸ Thought for {}s, {} tokens", seconds, tokens),
muted,
));
let mut lines: Vec<Line<'static>> = vec![header];
for raw in summary.split('\n') {
lines.push(Line::from(Span::styled(format!(" {}", raw), muted)));
}
lines
}
pub fn working_line(frame_index: usize, theme: &Theme) -> Line<'static> {
const FRAMES: [&str; 4] = ["⠋", "⠙", "⠹", "⠸"];
let spinner = FRAMES[frame_index % 4];
Line::from(Span::styled(
format!("{} Working…", spinner),
Style::default()
.fg(theme.primary)
.add_modifier(Modifier::BOLD),
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tui::theme::{theme, ColorDepth};
fn default_theme() -> Theme {
theme(ColorDepth::TrueColor)
}
#[test]
fn trace_line_pending_nao_vazia() {
let t = default_theme();
let line = trace_line(TraceState::Pending, "ReadFile", false, &t);
assert!(!line.spans.is_empty());
let text: String = line.spans.iter().map(|s| s.content.as_ref()).collect();
assert!(text.contains("ReadFile"));
assert!(text.contains('●'));
}
#[test]
fn trace_line_running_nao_vazia() {
let t = default_theme();
let line = trace_line(TraceState::Running, "Bash", false, &t);
assert!(!line.spans.is_empty());
let text: String = line.spans.iter().map(|s| s.content.as_ref()).collect();
assert!(text.contains("Bash"));
}
#[test]
fn trace_line_done_nao_vazia() {
let t = default_theme();
let line = trace_line(TraceState::Done, "WriteFile", false, &t);
assert!(!line.spans.is_empty());
let text: String = line.spans.iter().map(|s| s.content.as_ref()).collect();
assert!(text.contains("WriteFile"));
}
#[test]
fn trace_line_error_nao_vazia() {
let t = default_theme();
let line = trace_line(TraceState::Error, "Deploy", false, &t);
assert!(!line.spans.is_empty());
let text: String = line.spans.iter().map(|s| s.content.as_ref()).collect();
assert!(text.contains("Deploy"));
}
#[test]
fn trace_line_dot_pending_usa_text_muted() {
let t = default_theme();
let line = trace_line(TraceState::Pending, "op", false, &t);
let dot_span = &line.spans[0];
assert_eq!(dot_span.style.fg, Some(t.text_muted));
}
#[test]
fn trace_line_dot_running_usa_warning() {
let t = default_theme();
let line = trace_line(TraceState::Running, "op", false, &t);
let dot_span = &line.spans[0];
assert_eq!(dot_span.style.fg, Some(t.warning));
}
#[test]
fn trace_line_dot_done_usa_success() {
let t = default_theme();
let line = trace_line(TraceState::Done, "op", false, &t);
let dot_span = &line.spans[0];
assert_eq!(dot_span.style.fg, Some(t.success));
}
#[test]
fn trace_line_dot_error_usa_error() {
let t = default_theme();
let line = trace_line(TraceState::Error, "op", false, &t);
let dot_span = &line.spans[0];
assert_eq!(dot_span.style.fg, Some(t.error));
}
#[test]
fn trace_line_expandable_contem_sufixo() {
let t = default_theme();
let line = trace_line(TraceState::Done, "op", true, &t);
let text: String = line.spans.iter().map(|s| s.content.as_ref()).collect();
assert!(text.contains("ctrl+o expandir"));
}
#[test]
fn trace_line_nao_expandable_sem_sufixo() {
let t = default_theme();
let line = trace_line(TraceState::Done, "op", false, &t);
let text: String = line.spans.iter().map(|s| s.content.as_ref()).collect();
assert!(!text.contains("ctrl+o expandir"));
}
#[test]
fn thought_block_linha_simples() {
let t = default_theme();
let lines = thought_block(3, 120, "Analisando dependências", &t);
assert!(lines.len() >= 2); let header: String = lines[0].spans.iter().map(|s| s.content.as_ref()).collect();
assert!(header.contains("Thought for 3s, 120 tokens"));
let body: String = lines[1].spans.iter().map(|s| s.content.as_ref()).collect();
assert!(body.contains("Analisando dependências"));
}
#[test]
fn thought_block_summary_multilinha() {
let t = default_theme();
let summary = "Primeira linha\nSegunda linha\nTerceira linha";
let lines = thought_block(10, 500, summary, &t);
assert_eq!(lines.len(), 4);
let l1: String = lines[1].spans.iter().map(|s| s.content.as_ref()).collect();
let l2: String = lines[2].spans.iter().map(|s| s.content.as_ref()).collect();
let l3: String = lines[3].spans.iter().map(|s| s.content.as_ref()).collect();
assert!(l1.contains("Primeira linha"));
assert!(l2.contains("Segunda linha"));
assert!(l3.contains("Terceira linha"));
}
#[test]
fn thought_block_cabecalho_contem_seconds_e_tokens() {
let t = default_theme();
let lines = thought_block(7, 999, "resumo", &t);
let header: String = lines[0].spans.iter().map(|s| s.content.as_ref()).collect();
assert!(header.contains("7s"));
assert!(header.contains("999 tokens"));
}
#[test]
fn thought_block_linhas_recuadas() {
let t = default_theme();
let lines = thought_block(1, 10, "recuo", &t);
let body: String = lines[1].spans.iter().map(|s| s.content.as_ref()).collect();
assert!(body.starts_with(" "));
}
#[test]
fn working_line_frame_0() {
let t = default_theme();
let line = working_line(0, &t);
let text: String = line.spans.iter().map(|s| s.content.as_ref()).collect();
assert!(text.contains("⠋"));
assert!(text.contains("Working"));
}
#[test]
fn working_line_frame_1() {
let t = default_theme();
let line = working_line(1, &t);
let text: String = line.spans.iter().map(|s| s.content.as_ref()).collect();
assert!(text.contains("⠙"));
}
#[test]
fn working_line_frame_2() {
let t = default_theme();
let line = working_line(2, &t);
let text: String = line.spans.iter().map(|s| s.content.as_ref()).collect();
assert!(text.contains("⠹"));
}
#[test]
fn working_line_frame_3() {
let t = default_theme();
let line = working_line(3, &t);
let text: String = line.spans.iter().map(|s| s.content.as_ref()).collect();
assert!(text.contains("⠸"));
}
#[test]
fn working_line_ciclo_correto() {
let t = default_theme();
let line4 = working_line(4, &t);
let line0 = working_line(0, &t);
let text4: String = line4.spans.iter().map(|s| s.content.as_ref()).collect();
let text0: String = line0.spans.iter().map(|s| s.content.as_ref()).collect();
assert_eq!(text4, text0);
}
#[test]
fn working_line_nao_vazia() {
let t = default_theme();
for i in 0..8 {
let line = working_line(i, &t);
assert!(!line.spans.is_empty(), "frame {} nao deve ser vazio", i);
}
}
#[test]
fn working_line_usa_cor_primary() {
let t = default_theme();
let line = working_line(0, &t);
assert_eq!(line.spans[0].style.fg, Some(t.primary));
}
}