use super::*;
use ratatui::{
Terminal,
backend::TestBackend,
layout::{Constraint, Direction},
style::Color,
};
fn sample_search() -> SearchData {
SearchData {
version: "0.3.63".into(),
uptime_secs: 7440,
indexes: vec![
IndexRow {
id: "cto".into(),
chunk_count: 1_200,
root_path: "/tmp/cto".into(),
..Default::default()
},
IndexRow {
id: "trusty".into(),
chunk_count: 18_994,
root_path: "/tmp/trusty".into(),
..Default::default()
},
],
}
}
fn sample_memory() -> MemoryData {
MemoryData {
version: "0.4.2".into(),
palace_count: 2,
total_drawers: 14,
total_vectors: 8_400,
total_kg_triples: 1_200,
palaces: vec![
PalaceRow {
id: "default".into(),
name: "default".into(),
vector_count: 8_400,
..Default::default()
},
PalaceRow {
id: "work".into(),
name: "work".into(),
vector_count: 0,
..Default::default()
},
],
}
}
#[test]
fn test_layout_wide() {
let (direction, constraints) = panel_layout(140);
assert_eq!(direction, Direction::Horizontal);
assert_eq!(
constraints,
[Constraint::Percentage(50), Constraint::Percentage(50)]
);
assert_eq!(panel_layout(WIDE_LAYOUT_MIN_COLS).0, Direction::Horizontal);
}
#[test]
fn test_layout_narrow() {
let (direction, constraints) = panel_layout(80);
assert_eq!(direction, Direction::Vertical);
assert_eq!(
constraints,
[Constraint::Percentage(50), Constraint::Percentage(50)]
);
}
#[test]
fn test_offline_panel_renders() {
let search: DaemonPanel<SearchData> = DaemonPanel {
status: PanelStatus::Offline {
last_error: "connection refused".into(),
},
base_url: "http://127.0.0.1:7878".into(),
};
let lines = search_panel_lines(&search);
assert!(lines.iter().any(|l| l.contains("connection refused")));
assert!(lines.iter().any(|l| l.contains("unreachable")));
let memory: DaemonPanel<MemoryData> = DaemonPanel {
status: PanelStatus::Offline {
last_error: "timeout".into(),
},
base_url: "http://127.0.0.1:7070".into(),
};
let mlines = memory_panel_lines(&memory);
assert!(mlines.iter().any(|l| l.contains("timeout")));
let state = DashboardState {
search,
memory,
focus: Focus::Search,
show_help: false,
last_action: None,
};
let backend = TestBackend::new(130, 30);
let mut terminal = Terminal::new(backend).expect("test terminal");
terminal
.draw(|f| render(f, &state))
.expect("offline render must not panic");
}
#[test]
fn test_uptime_format() {
assert_eq!(format_uptime(7440), "2h 4m");
assert_eq!(format_uptime(0), "0h 0m");
assert_eq!(format_uptime(59), "0h 0m");
assert_eq!(format_uptime(3600), "1h 0m");
assert_eq!(format_uptime(3661), "1h 1m");
}
#[test]
fn test_format_count() {
assert_eq!(format_count(0), "0");
assert_eq!(format_count(900), "900");
assert_eq!(format_count(1_200), "1,200");
assert_eq!(format_count(9_999), "9,999");
assert_eq!(format_count(19_400), "19.4k");
assert_eq!(format_count(10_000), "10.0k");
}
#[test]
fn test_search_total_chunks() {
assert_eq!(sample_search().total_chunks(), 20_194);
assert_eq!(SearchData::default().total_chunks(), 0);
}
#[test]
fn test_search_panel_renders() {
let panel = DaemonPanel {
status: PanelStatus::Online(sample_search()),
base_url: "http://127.0.0.1:7878".into(),
};
let lines = search_panel_lines(&panel);
assert!(
lines
.iter()
.any(|l| l.contains("Uptime:") && l.contains("2h 4m"))
);
assert!(
lines
.iter()
.any(|l| l.contains("Indexes:") && l.contains('2'))
);
assert!(
lines
.iter()
.any(|l| l.contains("cto") && l.contains("1,200"))
);
assert!(
lines
.iter()
.any(|l| l.contains("trusty") && l.contains("19.0k"))
);
}
#[test]
fn test_memory_panel_renders() {
let panel = DaemonPanel {
status: PanelStatus::Online(sample_memory()),
base_url: "http://127.0.0.1:7070".into(),
};
let lines = memory_panel_lines(&panel);
assert!(
lines
.iter()
.any(|l| l.contains("Palaces:") && l.contains('2'))
);
assert!(
lines
.iter()
.any(|l| l.contains("Vectors:") && l.contains("8,400"))
);
assert!(
lines
.iter()
.any(|l| l.contains("KG triples:") && l.contains("1,200"))
);
assert!(
lines
.iter()
.any(|l| l.contains("default") && l.contains("8,400"))
);
}
#[test]
fn test_toggle_focus() {
let mut state = DashboardState::new("http://a", "http://b");
assert_eq!(state.focus, Focus::Search);
state.toggle_focus();
assert_eq!(state.focus, Focus::Memory);
state.toggle_focus();
assert_eq!(state.focus, Focus::Search);
}
#[test]
fn test_new_state_starts_connecting() {
let state = DashboardState::new("http://a", "http://b");
assert!(matches!(state.search.status, PanelStatus::Connecting));
assert!(matches!(state.memory.status, PanelStatus::Connecting));
assert_eq!(state.search.base_url, "http://a");
assert_eq!(state.memory.base_url, "http://b");
}
#[test]
fn test_panel_starts_connecting() {
let panel: DaemonPanel<SearchData> = DaemonPanel::new("http://x");
assert!(matches!(panel.status, PanelStatus::Connecting));
assert_eq!(panel.base_url, "http://x");
}
#[test]
fn test_panel_status_is_online() {
let online: PanelStatus<u32> = PanelStatus::Online(1);
assert!(online.is_online());
let offline: PanelStatus<u32> = PanelStatus::Offline {
last_error: "x".into(),
};
assert!(!offline.is_online());
let connecting: PanelStatus<u32> = PanelStatus::Connecting;
assert!(!connecting.is_online());
}
#[test]
fn test_reindex_target() {
let mut state = DashboardState::new("http://a", "http://b");
assert_eq!(state.reindex_target(), None);
state.search.status = PanelStatus::Online(sample_search());
assert_eq!(state.reindex_target(), Some("cto".to_string()));
state.focus = Focus::Memory;
assert_eq!(state.reindex_target(), None);
}
#[test]
fn test_status_badge() {
let online: PanelStatus<u32> = PanelStatus::Online(0);
assert_eq!(status_badge(&online), ('●', "ONLINE", Color::Green));
let connecting: PanelStatus<u32> = PanelStatus::Connecting;
assert_eq!(
status_badge(&connecting),
('◌', "CONNECTING", Color::Yellow)
);
let offline: PanelStatus<u32> = PanelStatus::Offline {
last_error: "x".into(),
};
assert_eq!(status_badge(&offline), ('○', "OFFLINE", Color::Red));
}
#[test]
fn test_truncate() {
assert_eq!(truncate("short", 16), "short");
assert_eq!(truncate("0123456789abcdefghij", 8), "0123456…");
assert_eq!(truncate("exactlyeight", 12), "exactlyeight");
}
#[test]
fn test_palace_row_project() {
let row = PalaceRow {
id: "trusty-search".into(),
name: "trusty-search".into(),
description: Some("Auto-registered from /Users/masa/Projects/trusty-search".into()),
..Default::default()
};
assert_eq!(row.project(), "trusty-search");
let bare = PalaceRow {
id: "p1".into(),
name: "notes".into(),
..Default::default()
};
assert_eq!(bare.project(), "notes");
let weird = PalaceRow {
id: "p2".into(),
name: "weird".into(),
description: Some("hand-made palace".into()),
..Default::default()
};
assert_eq!(weird.project(), "weird");
let trailing = PalaceRow {
id: "p3".into(),
name: "fallback".into(),
description: Some("Auto-registered from /tmp/".into()),
..Default::default()
};
assert_eq!(trailing.project(), "fallback");
}
#[test]
fn test_index_row_project() {
let row = IndexRow {
id: "trusty".into(),
root_path: "/Users/masa/Projects/trusty-tools".into(),
..Default::default()
};
assert_eq!(row.project(), "trusty-tools");
let bare = IndexRow {
id: "p1".into(),
..Default::default()
};
assert_eq!(bare.project(), "p1");
let trailing = IndexRow {
id: "p2".into(),
root_path: "/tmp/repo/".into(),
..Default::default()
};
assert_eq!(trailing.project(), "repo");
}
#[test]
fn test_help_text_lists_bindings() {
let text = help_text();
for token in ["Tab", "r ", "?", "Esc", "q "] {
assert!(text.contains(token), "help text missing {token}");
}
}
#[test]
fn test_render_smoke() {
let state = DashboardState {
search: DaemonPanel {
status: PanelStatus::Online(sample_search()),
base_url: "http://127.0.0.1:7878".into(),
},
memory: DaemonPanel {
status: PanelStatus::Online(sample_memory()),
base_url: "http://127.0.0.1:7070".into(),
},
focus: Focus::Memory,
show_help: true,
last_action: Some("reindex queued".into()),
};
for (w, h) in [(140u16, 30u16), (90, 40)] {
let backend = TestBackend::new(w, h);
let mut terminal = Terminal::new(backend).expect("test terminal");
terminal
.draw(|f| render(f, &state))
.expect("render must not panic");
}
}