use presentar_core::{Canvas, Color, Constraints, Point, Rect, Size, TextStyle, Widget};
use presentar_terminal::widgets::{
CellValue, ClusterAlgorithm, ClusterPlot, Column, DataFrame, EigenPlotType, FeatureImportance,
PCAPlot, ParallelCoordinates, RadarPlot, RadarSeries,
};
struct TestCanvas {
texts: Vec<(String, Point)>,
}
impl TestCanvas {
fn new(_width: usize, _height: usize) -> Self {
Self { texts: vec![] }
}
fn rendered_text(&self) -> String {
self.texts
.iter()
.map(|(t, _)| t.as_str())
.collect::<Vec<_>>()
.join(" ")
}
}
impl Canvas for TestCanvas {
fn fill_rect(&mut self, _rect: Rect, _color: Color) {}
fn stroke_rect(&mut self, _rect: Rect, _color: Color, _width: f32) {}
fn draw_text(&mut self, text: &str, position: Point, _style: &TextStyle) {
self.texts.push((text.to_string(), position));
}
fn draw_line(&mut self, _from: Point, _to: Point, _color: Color, _width: f32) {}
fn fill_circle(&mut self, _center: Point, _radius: f32, _color: Color) {}
fn stroke_circle(&mut self, _center: Point, _radius: f32, _color: Color, _width: f32) {}
fn fill_arc(&mut self, _c: Point, _r: f32, _s: f32, _e: f32, _color: Color) {}
fn draw_path(&mut self, _points: &[Point], _color: Color, _width: f32) {}
fn fill_polygon(&mut self, _points: &[Point], _color: Color) {}
fn push_clip(&mut self, _rect: Rect) {}
fn pop_clip(&mut self) {}
fn push_transform(&mut self, _transform: presentar_core::Transform2D) {}
fn pop_transform(&mut self) {}
}
#[test]
fn f_ml_001_dataframe_creates() {
let df = DataFrame::new().with_column(Column::from_f64("Values", &[1.0, 2.0, 3.0]));
assert_eq!(df.row_count(), 3);
assert_eq!(df.column_count(), 1);
}
#[test]
fn f_ml_002_dataframe_renders() {
let mut df = DataFrame::new()
.with_column(Column::from_f64("A", &[1.0, 2.0]))
.with_header(true);
let mut canvas = TestCanvas::new(40, 10);
df.layout(Rect::new(0.0, 0.0, 40.0, 10.0));
df.paint(&mut canvas);
}
#[test]
fn f_ml_003_dataframe_sparkline() {
let df = DataFrame::new().with_column(Column::sparkline_from_rows(
"Trend",
vec![vec![1.0, 2.0, 3.0], vec![3.0, 2.0, 1.0]],
));
assert_eq!(df.row_count(), 2);
}
#[test]
fn f_ml_004_dataframe_empty() {
let mut df = DataFrame::new();
let mut canvas = TestCanvas::new(40, 10);
df.layout(Rect::new(0.0, 0.0, 40.0, 10.0));
df.paint(&mut canvas);
}
#[test]
fn f_ml_005_dataframe_selection() {
let mut df = DataFrame::new().with_column(Column::from_f64("A", &[1.0, 2.0, 3.0]));
df.select_row(Some(1));
}
#[test]
fn f_ml_006_cellvalue_sparkline() {
let (s, _) = CellValue::Sparkline(vec![1.0, 3.0, 2.0, 5.0]).render(8);
let has_blocks = s.chars().any(|c| "▁▂▃▄▅▆▇█".contains(c));
assert!(has_blocks, "F-ML-006: Sparkline must render blocks");
}
#[test]
fn f_ml_007_cellvalue_trend() {
let (up, _) = CellValue::TrendArrow(0.5).render(5);
assert!(up.contains('↑'), "F-ML-007: Up trend must show ↑");
let (down, _) = CellValue::TrendArrow(-0.5).render(5);
assert!(down.contains('↓'), "F-ML-007: Down trend must show ↓");
}
#[test]
fn f_ml_008_cellvalue_progress() {
let (s, _) = CellValue::ProgressBar(0.5).render(10);
assert!(
s.contains('%') || s.contains('█'),
"F-ML-008: Progress must render"
);
}
#[test]
fn f_ml_009_cellvalue_null() {
let (s, _) = CellValue::Null.render(5);
assert!(s.is_empty() || s.contains('-'), "F-ML-009: Null renders");
}
#[test]
fn f_ml_010_cellvalue_bool() {
let (t, _) = CellValue::Bool(true).render(5);
let (f, _) = CellValue::Bool(false).render(5);
assert!(
t.contains('✓') || t.contains("true"),
"F-ML-010: True must render"
);
assert!(
f.contains('✗') || f.contains("false"),
"F-ML-010: False must render"
);
}
#[test]
fn f_ml_011_cluster_plot_create() {
let plot = ClusterPlot::new(vec![(0.0, 0.0), (1.0, 1.0)], vec![0, 1]);
let _ = plot.measure(Constraints::loose(Size::new(40.0, 20.0)));
}
#[test]
fn f_ml_012_cluster_plot_kmeans() {
let plot = ClusterPlot::new(vec![(0.0, 0.0)], vec![0])
.with_algorithm(ClusterAlgorithm::KMeans { k: 2 });
let _ = plot.measure(Constraints::loose(Size::new(40.0, 20.0)));
}
#[test]
fn f_ml_013_cluster_plot_dbscan() {
let plot =
ClusterPlot::new(vec![(0.0, 0.0)], vec![0]).with_algorithm(ClusterAlgorithm::DBSCAN {
eps: 0.5,
min_samples: 5,
});
let _ = plot.measure(Constraints::loose(Size::new(40.0, 20.0)));
}
#[test]
fn f_ml_014_cluster_plot_renders() {
let mut plot = ClusterPlot::new(vec![(0.0, 0.0), (1.0, 1.0)], vec![0, 1]);
let mut canvas = TestCanvas::new(40, 20);
plot.layout(Rect::new(0.0, 0.0, 40.0, 20.0));
plot.paint(&mut canvas);
}
#[test]
fn f_ml_015_cluster_plot_centroids() {
let mut plot = ClusterPlot::new(vec![(0.0, 0.0)], vec![0])
.with_centroids(vec![(0.0, 0.0)])
.with_show_centroids(true);
let mut canvas = TestCanvas::new(40, 20);
plot.layout(Rect::new(0.0, 0.0, 40.0, 20.0));
plot.paint(&mut canvas);
}
#[test]
fn f_ml_016_cluster_plot_empty() {
let mut plot = ClusterPlot::new(vec![], vec![]);
let mut canvas = TestCanvas::new(40, 20);
plot.layout(Rect::new(0.0, 0.0, 40.0, 20.0));
plot.paint(&mut canvas);
}
#[test]
fn f_ml_017_pca_plot_create() {
let plot = PCAPlot::new(vec![(0.0, 0.0), (1.0, 2.0)]);
let _ = plot.measure(Constraints::loose(Size::new(40.0, 20.0)));
}
#[test]
fn f_ml_018_pca_scree_plot() {
let mut plot = PCAPlot::new(vec![(0.0, 0.0)])
.with_eigenvalues(vec![3.0, 2.0, 1.0])
.with_plot_type(EigenPlotType::Scree);
let mut canvas = TestCanvas::new(40, 20);
plot.layout(Rect::new(0.0, 0.0, 40.0, 20.0));
plot.paint(&mut canvas);
}
#[test]
fn f_ml_019_pca_cumulative_plot() {
let mut plot = PCAPlot::new(vec![(0.0, 0.0)])
.with_eigenvalues(vec![3.0, 2.0, 1.0])
.with_plot_type(EigenPlotType::Cumulative);
let mut canvas = TestCanvas::new(40, 20);
plot.layout(Rect::new(0.0, 0.0, 40.0, 20.0));
plot.paint(&mut canvas);
}
#[test]
fn f_ml_020_pca_empty() {
let mut plot = PCAPlot::new(vec![]);
let mut canvas = TestCanvas::new(40, 20);
plot.layout(Rect::new(0.0, 0.0, 40.0, 20.0));
plot.paint(&mut canvas);
}
#[test]
fn f_ml_021_parallel_coords_create() {
let plot =
ParallelCoordinates::new(vec!["X".to_string(), "Y".to_string()], vec![vec![0.5, 0.8]]);
let _ = plot.measure(Constraints::loose(Size::new(40.0, 15.0)));
}
#[test]
fn f_ml_022_parallel_coords_renders() {
let mut plot =
ParallelCoordinates::new(vec!["A".to_string(), "B".to_string()], vec![vec![0.5, 0.8]]);
let mut canvas = TestCanvas::new(40, 15);
plot.layout(Rect::new(0.0, 0.0, 40.0, 15.0));
plot.paint(&mut canvas);
}
#[test]
fn f_ml_023_parallel_coords_empty() {
let mut plot = ParallelCoordinates::new(vec![], vec![]);
let mut canvas = TestCanvas::new(40, 15);
plot.layout(Rect::new(0.0, 0.0, 40.0, 15.0));
plot.paint(&mut canvas);
}
#[test]
fn f_ml_024_radar_plot_create() {
let plot = RadarPlot::new(vec!["A".to_string(), "B".to_string(), "C".to_string()]);
let _ = plot.measure(Constraints::loose(Size::new(40.0, 20.0)));
}
#[test]
fn f_ml_025_radar_plot_polygon() {
let series = RadarSeries::new("P1", vec![8.0, 6.0, 7.0], Color::BLUE);
let mut plot =
RadarPlot::new(vec!["A".to_string(), "B".to_string(), "C".to_string()]).with_series(series);
let mut canvas = TestCanvas::new(40, 20);
plot.layout(Rect::new(0.0, 0.0, 40.0, 20.0));
plot.paint(&mut canvas);
}
#[test]
fn f_ml_026_radar_plot_multi_series() {
let s1 = RadarSeries::new("A", vec![8.0, 6.0, 7.0], Color::BLUE);
let s2 = RadarSeries::new("B", vec![6.0, 8.0, 5.0], Color::RED);
let mut plot = RadarPlot::new(vec!["X".to_string(), "Y".to_string(), "Z".to_string()])
.with_series(s1)
.with_series(s2);
let mut canvas = TestCanvas::new(40, 20);
plot.layout(Rect::new(0.0, 0.0, 40.0, 20.0));
plot.paint(&mut canvas);
}
#[test]
fn f_ml_027_radar_plot_grid() {
let mut plot =
RadarPlot::new(vec!["A".to_string(), "B".to_string(), "C".to_string()]).with_grid(true);
let mut canvas = TestCanvas::new(40, 20);
plot.layout(Rect::new(0.0, 0.0, 40.0, 20.0));
plot.paint(&mut canvas);
}
#[test]
fn f_ml_028_radar_plot_empty() {
let mut plot = RadarPlot::new(vec![]);
let mut canvas = TestCanvas::new(40, 20);
plot.layout(Rect::new(0.0, 0.0, 40.0, 20.0));
plot.paint(&mut canvas);
}
#[test]
fn f_ml_029_feature_importance_create() {
let plot = FeatureImportance::new(vec!["age".to_string()], vec![0.5]);
let _ = plot.measure(Constraints::loose(Size::new(60.0, 10.0)));
}
#[test]
fn f_ml_030_feature_importance_bars() {
let mut plot = FeatureImportance::new(vec!["A".to_string(), "B".to_string()], vec![0.5, 0.3]);
let mut canvas = TestCanvas::new(60, 10);
plot.layout(Rect::new(0.0, 0.0, 60.0, 10.0));
plot.paint(&mut canvas);
let output = canvas.rendered_text();
assert!(output.contains('█'), "F-ML-030: Must render bars");
}
#[test]
fn f_ml_031_feature_importance_values() {
let mut plot = FeatureImportance::new(vec!["A".to_string()], vec![0.5]).with_show_values(true);
let mut canvas = TestCanvas::new(60, 5);
plot.layout(Rect::new(0.0, 0.0, 60.0, 5.0));
plot.paint(&mut canvas);
let output = canvas.rendered_text();
assert!(
output.contains("0.5") || output.contains('.'),
"F-ML-031: Values should display"
);
}
#[test]
fn f_ml_032_feature_importance_sorted() {
let plot = FeatureImportance::new(vec!["Low".to_string(), "High".to_string()], vec![0.1, 0.9])
.with_sorted(true);
let _ = plot.measure(Constraints::loose(Size::new(60.0, 10.0)));
}
#[test]
fn f_ml_033_feature_importance_empty() {
let mut plot = FeatureImportance::default();
let mut canvas = TestCanvas::new(60, 10);
plot.layout(Rect::new(0.0, 0.0, 60.0, 10.0));
plot.paint(&mut canvas);
}
#[test]
fn f_ml_034_feature_importance_truncate() {
let mut plot = FeatureImportance::new(vec!["very_long_feature_name".to_string()], vec![0.5]);
let mut canvas = TestCanvas::new(60, 5);
plot.layout(Rect::new(0.0, 0.0, 60.0, 5.0));
plot.paint(&mut canvas);
}
#[test]
fn f_ml_035_ml_widgets_clone() {
let _ = DataFrame::new().clone();
let _ = ClusterPlot::new(vec![], vec![]).clone();
let _ = PCAPlot::new(vec![]).clone();
let _ = ParallelCoordinates::new(vec![], vec![]).clone();
let _ = RadarPlot::new(vec![]).clone();
let _ = FeatureImportance::default().clone();
}
#[test]
fn f_ml_036_ml_widgets_measure() {
let c = Constraints::loose(Size::new(100.0, 50.0));
let _ = DataFrame::new().measure(c);
let _ = ClusterPlot::new(vec![], vec![]).measure(c);
let _ = PCAPlot::new(vec![]).measure(c);
let _ = ParallelCoordinates::new(vec![], vec![]).measure(c);
let _ = RadarPlot::new(vec![]).measure(c);
let _ = FeatureImportance::default().measure(c);
}
#[test]
fn f_ml_037_ml_widgets_zero_bounds() {
let z = Rect::new(0.0, 0.0, 0.0, 0.0);
DataFrame::new().layout(z);
ClusterPlot::new(vec![], vec![]).layout(z);
PCAPlot::new(vec![]).layout(z);
}
#[test]
fn f_ml_038_ml_widgets_nan() {
let mut fi = FeatureImportance::new(vec!["A".to_string()], vec![f64::NAN]);
let mut canvas = TestCanvas::new(60, 5);
fi.layout(Rect::new(0.0, 0.0, 60.0, 5.0));
fi.paint(&mut canvas);
}
#[test]
fn f_ml_039_ml_widgets_large_value() {
let mut fi = FeatureImportance::new(vec!["A".to_string()], vec![1e10]);
let mut canvas = TestCanvas::new(60, 5);
fi.layout(Rect::new(0.0, 0.0, 60.0, 5.0));
fi.paint(&mut canvas);
}
#[test]
fn f_ml_040_cellvalue_spark_bar() {
let (s, _) = CellValue::SparkBar(vec![1.0, 3.0, 2.0]).render(8);
let has_chars = s.chars().any(|c| "█▓▒░".contains(c));
assert!(has_chars, "F-ML-040: SparkBar must render");
}
#[test]
fn f_ml_041_cellvalue_spark_win_loss() {
let (s, _) = CellValue::SparkWinLoss(vec![1, -1, 0]).render(6);
let has_chars = s.chars().any(|c| "▲▼─".contains(c));
assert!(has_chars, "F-ML-041: SparkWinLoss must render");
}
#[test]
fn f_ml_042_cellvalue_micro_bar() {
let (s, _) = CellValue::MicroBar {
value: 75.0,
max: 100.0,
}
.render(10);
assert!(!s.is_empty(), "F-ML-042: MicroBar must render");
}
#[test]
fn f_ml_043_pca_biplot() {
let mut plot = PCAPlot::new(vec![(0.0, 0.0)])
.with_eigenvalues(vec![3.0, 2.0])
.with_plot_type(EigenPlotType::Biplot);
let mut canvas = TestCanvas::new(40, 20);
plot.layout(Rect::new(0.0, 0.0, 40.0, 20.0));
plot.paint(&mut canvas);
}
#[test]
fn f_ml_044_pca_loadings() {
let mut plot = PCAPlot::new(vec![(0.0, 0.0)])
.with_eigenvalues(vec![3.0, 2.0])
.with_plot_type(EigenPlotType::Loadings);
let mut canvas = TestCanvas::new(40, 20);
plot.layout(Rect::new(0.0, 0.0, 40.0, 20.0));
plot.paint(&mut canvas);
}
#[test]
fn f_ml_045_cluster_hierarchical() {
let plot = ClusterPlot::new(vec![(0.0, 0.0)], vec![0])
.with_algorithm(ClusterAlgorithm::Hierarchical { n_clusters: 2 });
let _ = plot.measure(Constraints::loose(Size::new(40.0, 20.0)));
}
#[test]
fn f_ml_046_cluster_hdbscan() {
let plot =
ClusterPlot::new(vec![(0.0, 0.0)], vec![0]).with_algorithm(ClusterAlgorithm::HDBSCAN {
min_cluster_size: 5,
});
let _ = plot.measure(Constraints::loose(Size::new(40.0, 20.0)));
}
#[test]
fn f_ml_047_radar_fill_alpha() {
let mut plot = RadarPlot::new(vec!["A".to_string(), "B".to_string(), "C".to_string()])
.with_fill(true)
.with_fill_alpha(0.5);
let mut canvas = TestCanvas::new(40, 20);
plot.layout(Rect::new(0.0, 0.0, 40.0, 20.0));
plot.paint(&mut canvas);
}
#[test]
fn f_ml_048_radar_labels() {
let mut plot = RadarPlot::new(vec!["A".to_string(), "B".to_string()]).with_labels(true);
let mut canvas = TestCanvas::new(40, 20);
plot.layout(Rect::new(0.0, 0.0, 40.0, 20.0));
plot.paint(&mut canvas);
}
#[test]
fn f_ml_049_feature_importance_max() {
let f: Vec<String> = (0..50).map(|i| format!("f{}", i)).collect();
let v: Vec<f64> = (0..50).map(|i| i as f64 / 50.0).collect();
let mut plot = FeatureImportance::new(f, v).with_max_features(10);
let mut canvas = TestCanvas::new(60, 15);
plot.layout(Rect::new(0.0, 0.0, 60.0, 15.0));
plot.paint(&mut canvas);
}
#[test]
fn f_ml_050_feature_importance_color() {
let mut plot = FeatureImportance::new(vec!["A".to_string()], vec![0.5])
.with_color(Color::new(1.0, 0.5, 0.0, 1.0));
let mut canvas = TestCanvas::new(60, 5);
plot.layout(Rect::new(0.0, 0.0, 60.0, 5.0));
plot.paint(&mut canvas);
}