#![cfg(feature = "dap")]
use pmat::services::dap::types::{ExecutionSnapshot, SourceLocation, StackFrame};
use std::collections::HashMap;
fn create_test_recording(count: usize) -> Vec<ExecutionSnapshot> {
let mut snapshots = Vec::new();
for i in 0..count {
let mut vars = HashMap::new();
vars.insert("i".to_string(), serde_json::json!(i));
snapshots.push(ExecutionSnapshot {
timestamp: 1000000 + (i as u64 * 1000),
sequence: i,
variables: vars,
call_stack: vec![StackFrame {
id: 1,
name: "main".to_string(),
source: None,
line: 10,
column: 0,
}],
location: SourceLocation {
file: "test.rs".to_string(),
line: 10,
column: Some(0),
},
delta: None,
});
}
snapshots
}
fn create_test_recording_with_vars() -> Vec<ExecutionSnapshot> {
let mut snapshots = Vec::new();
for i in 0..5 {
let mut vars = HashMap::new();
vars.insert("counter".to_string(), serde_json::json!(i * 10));
vars.insert("name".to_string(), serde_json::json!(format!("step_{}", i)));
snapshots.push(ExecutionSnapshot {
timestamp: 1000000 + (i as u64 * 1000),
sequence: i,
variables: vars,
call_stack: vec![
StackFrame {
id: 1,
name: "main".to_string(),
source: None,
line: (10 + i) as i64,
column: 0,
},
StackFrame {
id: 2,
name: "process".to_string(),
source: None,
line: (20 + i) as i64,
column: 0,
},
],
location: SourceLocation {
file: "test.rs".to_string(),
line: 10 + i,
column: Some(0),
},
delta: None,
});
}
snapshots
}
#[test]
fn test_render_timeline() {
let recording = create_test_recording(10);
let ui = pmat::services::dap::TimelineUI::new(recording);
let output = ui.render();
assert!(
output.contains("0") && output.contains("9"),
"Timeline should show start and end positions"
);
assert!(
output.contains("^") || output.contains("▼"),
"Timeline should have current position indicator"
);
}
#[test]
fn test_navigate_timeline() {
let recording = create_test_recording(10);
let mut ui = pmat::services::dap::TimelineUI::new(recording);
assert_eq!(ui.current_position(), 0, "Should start at position 0");
ui.handle_key('→').unwrap();
assert_eq!(ui.current_position(), 1, "Should move to position 1");
ui.handle_key('→').unwrap();
assert_eq!(ui.current_position(), 2, "Should move to position 2");
ui.handle_key('←').unwrap();
assert_eq!(ui.current_position(), 1, "Should move back to position 1");
}
#[test]
fn test_jump_to_snapshot() {
let recording = create_test_recording(10);
let mut ui = pmat::services::dap::TimelineUI::new(recording);
ui.jump_to(5).unwrap();
assert_eq!(ui.current_position(), 5, "Should jump to position 5");
let output = ui.render();
assert!(
output.contains("5") && (output.contains("^") || output.contains("▼")),
"Timeline should show indicator at position 5"
);
ui.jump_to(9).unwrap();
assert_eq!(ui.current_position(), 9, "Should jump to position 9");
}
#[test]
fn test_display_snapshot_details() {
let recording = create_test_recording_with_vars();
let mut ui = pmat::services::dap::TimelineUI::new(recording);
ui.jump_to(3).unwrap();
let details = ui.render_details();
assert!(
details.contains("Snapshot #3") || details.contains("Position: 3"),
"Details should show snapshot number"
);
assert!(
details.contains("Variables:"),
"Details should show variables section"
);
assert!(
details.contains("counter") && details.contains("30"),
"Details should show variable values"
);
assert!(
details.contains("Call Stack:") || details.contains("Stack:"),
"Details should show call stack section"
);
assert!(
details.contains("main") && details.contains("process"),
"Details should show function names"
);
assert!(
details.contains("Location:") || details.contains("File:"),
"Details should show location section"
);
assert!(details.contains("test.rs"), "Details should show file name");
}
#[test]
fn test_show_performance_metrics() {
let recording = create_test_recording(100);
let ui = pmat::services::dap::TimelineUI::new(recording);
let metrics = ui.render_metrics();
assert!(
metrics.contains("Total snapshots: 100") || metrics.contains("100 snapshots"),
"Metrics should show total snapshot count"
);
assert!(
metrics.contains("size") || metrics.contains("Size"),
"Metrics should show recording size"
);
assert!(
metrics.contains("ratio") || metrics.contains("Ratio") || !metrics.is_empty(),
"Metrics should show compression info or other stats"
);
}
#[test]
fn test_handle_empty_recording() {
let empty_recording = Vec::new();
let result = std::panic::catch_unwind(|| pmat::services::dap::TimelineUI::new(empty_recording));
assert!(
result.is_ok() || result.is_err(),
"Should handle empty recording gracefully"
);
}
#[test]
fn test_timeline_width_adjustment() {
let recording = create_test_recording(50);
let ui = pmat::services::dap::TimelineUI::new(recording);
let output_narrow = ui.render_with_width(40);
let output_wide = ui.render_with_width(120);
assert!(!output_narrow.is_empty(), "Narrow timeline should render");
assert!(!output_wide.is_empty(), "Wide timeline should render");
assert!(
output_wide.len() >= output_narrow.len(),
"Wide timeline should have equal or more content"
);
}
#[test]
fn test_navigate_beyond_limits() {
let recording = create_test_recording(5); let mut ui = pmat::services::dap::TimelineUI::new(recording);
let result = ui.handle_key('←');
assert!(
result.is_err(),
"Should not be able to step backward from position 0"
);
assert_eq!(ui.current_position(), 0, "Position should remain at 0");
ui.jump_to(4).unwrap();
let result = ui.handle_key('→');
assert!(
result.is_err(),
"Should not be able to step forward from last position"
);
assert_eq!(ui.current_position(), 4, "Position should remain at 4");
let result = ui.jump_to(10);
assert!(
result.is_err(),
"Should not be able to jump beyond recording length"
);
}
#[test]
fn test_color_coded_timeline() {
let recording = create_test_recording(10);
let ui = pmat::services::dap::TimelineUI::new(recording);
let colored_output = ui.render_colored();
assert!(
colored_output.contains("\x1b[") || !colored_output.is_empty(),
"Colored output should contain ANSI escape codes or content"
);
}
#[test]
fn test_timeline_rendering_performance() {
let large_recording = create_test_recording(1000);
let ui = pmat::services::dap::TimelineUI::new(large_recording);
let start = std::time::Instant::now();
let _output = ui.render();
let elapsed = start.elapsed();
assert!(
elapsed.as_millis() < 100,
"Timeline rendering should be <100ms, got {}ms",
elapsed.as_millis()
);
}