#![cfg(feature = "ptop")]
use presentar_terminal::direct::CellBuffer;
use presentar_terminal::ptop::app::MetricsSnapshot;
use presentar_terminal::ptop::{ui, App, PanelType};
use presentar_terminal::Snapshot;
fn render_to_string(app: &App, width: u16, height: u16) -> String {
let mut buffer = CellBuffer::new(width, height);
ui::draw(app, &mut buffer);
let mut output = String::new();
for y in 0..height {
for x in 0..width {
if let Some(cell) = buffer.get(x, y) {
output.push(cell.symbol.chars().next().unwrap_or(' '));
} else {
output.push(' ');
}
}
output.push('\n');
}
output
}
mod cpu_panel {
use super::*;
#[test]
fn renders_normal() {
let app = App::with_config(true, Default::default());
let output = render_to_string(&app, 120, 40);
assert!(output.contains("CPU"), "CPU panel must be visible");
}
#[test]
fn renders_exploded() {
let mut app = App::with_config(true, Default::default());
app.exploded_panel = Some(PanelType::Cpu);
let output = render_to_string(&app, 140, 45);
assert!(output.contains("CPU"), "CPU exploded panel must show");
}
#[test]
fn exploded_shows_async_freq() {
let mut app = App::with_config(true, Default::default());
app.exploded_panel = Some(PanelType::Cpu);
let mut snapshot = MetricsSnapshot::empty();
snapshot.per_core_freq = vec![4765; 48];
snapshot.per_core_temp = vec![65.0; 48];
snapshot.per_core_percent = vec![45.0; 48];
snapshot.cpu_avg = 0.45;
app.apply_snapshot(snapshot);
let output = render_to_string(&app, 140, 45);
let has_freq =
output.contains("4.76") || output.contains("4.77") || output.contains("4765");
assert!(has_freq, "Must show async-updated frequency");
}
#[test]
fn shows_utilization_histogram() {
let mut app = App::with_config(true, Default::default());
app.exploded_panel = Some(PanelType::Cpu);
let output = render_to_string(&app, 140, 45);
assert!(
output.contains("CORE UTILIZATION") || output.contains("BREAKDOWN"),
"Must show utilization breakdown"
);
}
}
mod memory_panel {
use super::*;
#[test]
fn renders() {
let app = App::with_config(true, Default::default());
let output = render_to_string(&app, 120, 40);
assert!(
output.contains("Mem") || output.contains("MEM") || output.contains("Memory"),
"Memory panel must be visible"
);
}
#[test]
fn shows_usage() {
let mut app = App::with_config(true, Default::default());
let mut snapshot = MetricsSnapshot::empty();
snapshot.mem_total = 32_000_000_000;
snapshot.mem_used = 16_000_000_000;
app.apply_snapshot(snapshot);
let output = render_to_string(&app, 120, 40);
assert!(
output.contains("G") || output.contains("GB") || output.contains("%"),
"Memory panel must show values"
);
}
}
mod process_panel {
use super::*;
#[test]
fn renders() {
let app = App::with_config(true, Default::default());
let output = render_to_string(&app, 120, 40);
assert!(
output.contains("PID") || output.contains("COMMAND") || output.contains("Process"),
"Process panel must be visible"
);
}
#[test]
fn shows_columns() {
let app = App::with_config(true, Default::default());
let output = render_to_string(&app, 120, 40);
let has_cpu = output.contains("CPU") || output.contains("%");
let has_mem = output.contains("MEM") || output.contains("Memory");
assert!(has_cpu, "Process panel must show CPU column or values");
}
}
mod network_panel {
use super::*;
#[test]
fn renders() {
let app = App::with_config(true, Default::default());
let output = render_to_string(&app, 120, 40);
assert!(
output.contains("Net")
|| output.contains("eth")
|| output.contains("lo")
|| output.contains("RX")
|| output.contains("TX")
|| output.contains("wl"),
"Network panel must be visible"
);
}
}
mod disk_panel {
use super::*;
#[test]
fn renders() {
let app = App::with_config(true, Default::default());
let output = render_to_string(&app, 120, 40);
assert!(
output.contains("Disk")
|| output.contains("sda")
|| output.contains("nvme")
|| output.contains("Read")
|| output.contains("Write"),
"Disk panel must be visible or show disk stats"
);
}
}
mod gpu_panel {
use super::*;
#[test]
fn renders() {
let mut app = App::with_config(true, Default::default());
app.panels.gpu = true;
let output = render_to_string(&app, 120, 40);
assert!(
output.contains("GPU") || output.contains("N/A") || output.contains("%"),
"GPU panel must be visible"
);
}
#[test]
fn renders_exploded() {
let mut app = App::with_config(true, Default::default());
app.exploded_panel = Some(PanelType::Gpu);
let _output = render_to_string(&app, 140, 45);
}
}
mod sensors_panel {
use super::*;
#[test]
fn renders() {
let mut app = App::with_config(true, Default::default());
app.panels.sensors = true;
let output = render_to_string(&app, 120, 40);
assert!(
output.contains("Sensor") || output.contains("°C") || output.contains("Temp"),
"Sensors panel must be visible"
);
}
#[test]
fn renders_exploded() {
let mut app = App::with_config(true, Default::default());
app.exploded_panel = Some(PanelType::Sensors);
let _output = render_to_string(&app, 140, 45);
}
}
mod connections_panel {
use super::*;
#[test]
fn renders() {
let mut app = App::with_config(true, Default::default());
app.panels.connections = true;
let output = render_to_string(&app, 120, 40);
assert!(
output.contains("Connect")
|| output.contains("TCP")
|| output.contains("ESTABLISHED")
|| output.contains("LISTEN"),
"Connections panel must be visible"
);
}
#[test]
fn renders_exploded() {
let mut app = App::with_config(true, Default::default());
app.exploded_panel = Some(PanelType::Connections);
let _output = render_to_string(&app, 140, 45);
}
}
mod psi_panel {
use super::*;
#[test]
fn renders() {
let mut app = App::with_config(true, Default::default());
app.panels.psi = true;
let output = render_to_string(&app, 120, 40);
assert!(
output.contains("PSI")
|| output.contains("Pressure")
|| output.contains("some")
|| output.contains("full"),
"PSI panel must be visible"
);
}
#[test]
fn renders_exploded() {
let mut app = App::with_config(true, Default::default());
app.exploded_panel = Some(PanelType::Psi);
let _output = render_to_string(&app, 140, 45);
}
}
mod files_panel {
use super::*;
#[test]
fn renders() {
let mut app = App::with_config(true, Default::default());
app.panels.files = true;
let output = render_to_string(&app, 120, 40);
assert!(
output.contains("File")
|| output.contains("/")
|| output.contains("home")
|| output.contains("root"),
"Files panel must be visible"
);
}
#[test]
fn renders_exploded() {
let mut app = App::with_config(true, Default::default());
app.exploded_panel = Some(PanelType::Files);
let _output = render_to_string(&app, 140, 45);
}
}
mod battery_panel {
use super::*;
#[test]
fn renders() {
let mut app = App::with_config(true, Default::default());
app.panels.battery = true;
let output = render_to_string(&app, 120, 40);
assert!(
output.contains("Batt")
|| output.contains("%")
|| output.contains("N/A")
|| output.contains("AC"),
"Battery panel must be visible"
);
}
#[test]
fn renders_exploded() {
let mut app = App::with_config(true, Default::default());
app.exploded_panel = Some(PanelType::Battery);
let _output = render_to_string(&app, 140, 45);
}
}
mod containers_panel {
use super::*;
#[test]
fn renders() {
let mut app = App::with_config(true, Default::default());
let output = render_to_string(&app, 120, 40);
let _ = output;
}
#[test]
fn renders_exploded() {
let mut app = App::with_config(true, Default::default());
app.exploded_panel = Some(PanelType::Containers);
let _output = render_to_string(&app, 140, 45);
}
}
mod no_placeholders {
use super::*;
#[test]
fn no_placeholder_text() {
let app = App::with_config(true, Default::default());
let output = render_to_string(&app, 120, 40);
let placeholders = [
"coconut radio",
"lorem ipsum",
"placeholder",
"TODO",
"FIXME",
];
for p in placeholders {
assert!(
!output.to_lowercase().contains(p),
"Output must not contain placeholder text: {}",
p
);
}
}
#[test]
fn minimum_size_no_panic() {
let app = App::with_config(true, Default::default());
let _output = render_to_string(&app, 40, 10);
}
#[test]
fn large_size_no_panic() {
let app = App::with_config(true, Default::default());
let _output = render_to_string(&app, 200, 60);
}
}
mod exploded_view {
use super::*;
#[test]
fn all_panels_explode() {
let panels = [
PanelType::Cpu,
PanelType::Memory,
PanelType::Disk,
PanelType::Network,
PanelType::Process,
PanelType::Gpu,
PanelType::Sensors,
PanelType::Connections,
PanelType::Psi,
PanelType::Files,
PanelType::Battery,
PanelType::Containers,
];
for panel in panels {
let mut app = App::with_config(true, Default::default());
app.exploded_panel = Some(panel);
let _output = render_to_string(&app, 140, 45);
}
}
#[test]
fn exploded_fills_screen() {
let mut app = App::with_config(true, Default::default());
app.exploded_panel = Some(PanelType::Cpu);
let output = render_to_string(&app, 140, 45);
let lines: Vec<&str> = output.lines().collect();
let non_empty_lines = lines.iter().filter(|l| l.trim().len() > 5).count();
assert!(
non_empty_lines > 20,
"Exploded view must fill screen, got {} lines",
non_empty_lines
);
}
}