sorting-race 0.2.0

Terminal-based sorting algorithm race visualization tool with real-time metrics
Documentation
//! Contract tests for BarChart renderer component
//!
//! These tests verify the expected interface and behavior of the BarChart visualization component.
//! The tests are designed to fail initially since the BarChart component is stubbed.

use ratatui::{
    backend::TestBackend,
    buffer::Buffer,
    layout::Rect,
    style::{Color, Style},
};

/// Placeholder BarChart component that should be implemented
/// This is a stub that will need to be replaced with the actual implementation
#[derive(Debug)]
pub struct BarChart {
    data: Vec<(String, u64)>,
    bar_width: u16,
    bar_gap: u16,
    bar_style: Style,
    value_style: Style,
    label_style: Style,
    max_height: u16,
}

impl BarChart {
    /// Create a new BarChart from array data
    pub fn new(data: Vec<(String, u64)>) -> Self {
        Self {
            data,
            bar_width: 3,
            bar_gap: 1,
            bar_style: Style::default(),
            value_style: Style::default(),
            label_style: Style::default(),
            max_height: 10,
        }
    }

    /// Set the style for bars
    pub fn bar_style(mut self, style: Style) -> Self {
        self.bar_style = style;
        self
    }

    /// Set the style for values
    pub fn value_style(mut self, style: Style) -> Self {
        self.value_style = style;
        self
    }

    /// Set the style for labels
    pub fn label_style(mut self, style: Style) -> Self {
        self.label_style = style;
        self
    }

    /// Set the maximum height for bars
    pub fn max_height(mut self, height: u16) -> Self {
        self.max_height = height;
        self
    }

    /// Set bar width
    pub fn bar_width(mut self, width: u16) -> Self {
        self.bar_width = width;
        self
    }

    /// Set gap between bars
    pub fn bar_gap(mut self, gap: u16) -> Self {
        self.bar_gap = gap;
        self
    }

    /// Render the bar chart to a ratatui buffer
    pub fn render(&self, _area: Rect, _buf: &mut Buffer) {
        // This is a stub implementation that will fail tests
        // The actual implementation should render bars based on the data
        unimplemented!("BarChart::render() is not yet implemented")
    }

    /// Convert array data to bar chart data with color mapping
    pub fn from_array_with_colors(array: &[i32], highlights: &[usize]) -> Self {
        let data: Vec<(String, u64)> = array
            .iter()
            .enumerate()
            .map(|(i, &value)| (i.to_string(), value as u64))
            .collect();

        let mut chart = Self::new(data);

        // Set colors based on operation type (compare vs swap)
        if highlights.len() == 2 {
            // Two highlights indicate a comparison
            chart = chart.bar_style(Style::default().fg(Color::Blue));
        } else if highlights.len() == 1 {
            // Single highlight indicates a swap
            chart = chart.bar_style(Style::default().fg(Color::Red));
        }

        chart
    }

    /// Scale the chart for different terminal sizes
    pub fn scale_for_terminal(mut self, terminal_width: u16, terminal_height: u16) -> Self {
        // Calculate appropriate bar width and max height based on terminal size
        let available_width = terminal_width.saturating_sub(4); // Leave margins
        let bar_count = self.data.len() as u16;

        if bar_count > 0 {
            let total_width_needed = bar_count * (self.bar_width + self.bar_gap);
            if total_width_needed > available_width {
                // Scale down bar width and gap
                let new_bar_width = (available_width / bar_count).saturating_sub(1).max(1);
                self.bar_width = new_bar_width;
                self.bar_gap = if new_bar_width > 1 { 1 } else { 0 };
            }
        }

        // Scale height to use available terminal space
        let available_height = terminal_height.saturating_sub(6); // Leave space for headers/labels
        self.max_height = available_height.min(20); // Cap at reasonable height

        self
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use ratatui::{Terminal, style::Color};

    #[test]
    fn test_bar_chart_creation_from_array_data() {
        // Test that BarChart can be created from array data
        let array_data = vec![5, 3, 8, 1, 9, 2];
        let highlights = vec![];

        let chart = BarChart::from_array_with_colors(&array_data, &highlights);

        // Verify the chart was created with correct data
        assert_eq!(chart.data.len(), 6);
        assert_eq!(chart.data[0], ("0".to_string(), 5));
        assert_eq!(chart.data[1], ("1".to_string(), 3));
        assert_eq!(chart.data[4], ("4".to_string(), 9));
    }

    #[test]
    fn test_color_mapping_for_operations() {
        let array_data = vec![5, 3, 8, 1];

        // Test comparison operation (two highlights = blue)
        let comparison_highlights = vec![0, 2];
        let comparison_chart =
            BarChart::from_array_with_colors(&array_data, &comparison_highlights);
        assert_eq!(comparison_chart.bar_style.fg, Some(Color::Blue));

        // Test swap operation (one highlight = red)
        let swap_highlights = vec![1];
        let swap_chart = BarChart::from_array_with_colors(&array_data, &swap_highlights);
        assert_eq!(swap_chart.bar_style.fg, Some(Color::Red));

        // Test no operation (no highlights = default)
        let no_highlights = vec![];
        let default_chart = BarChart::from_array_with_colors(&array_data, &no_highlights);
        assert_eq!(default_chart.bar_style.fg, None);
    }

    #[test]
    fn test_height_scaling_for_different_terminal_sizes() {
        let array_data = vec![1, 2, 3, 4, 5];
        let highlights = vec![];

        // Test small terminal
        let chart_small =
            BarChart::from_array_with_colors(&array_data, &highlights).scale_for_terminal(40, 10);
        assert!(chart_small.max_height <= 4); // Should scale down for small terminal

        // Test large terminal
        let chart_large =
            BarChart::from_array_with_colors(&array_data, &highlights).scale_for_terminal(120, 30);
        assert!(chart_large.max_height >= 10); // Should use more space in large terminal
        assert!(chart_large.max_height <= 20); // But cap at reasonable limit
    }

    #[test]
    fn test_bar_width_scaling_for_many_elements() {
        let array_data: Vec<i32> = (0..50).collect(); // 50 elements
        let highlights = vec![];

        // Test narrow terminal with many elements
        let chart =
            BarChart::from_array_with_colors(&array_data, &highlights).scale_for_terminal(60, 20);

        // Should scale down bar width to fit
        assert!(chart.bar_width <= 2);

        // Total width should not exceed available space
        let total_width = array_data.len() as u16 * (chart.bar_width + chart.bar_gap);
        assert!(total_width <= 56); // 60 - 4 margin
    }

    #[test]
    #[should_panic(expected = "BarChart::render() is not yet implemented")]
    fn test_rendering_to_ratatui_buffer_fails_as_expected() {
        // This test should fail because the render method is stubbed
        let array_data = vec![5, 3, 8, 1];
        let highlights = vec![0, 2];
        let chart = BarChart::from_array_with_colors(&array_data, &highlights);

        let backend = TestBackend::new(40, 10);
        let mut terminal = Terminal::new(backend).unwrap();

        terminal
            .draw(|f| {
                let area = f.area();
                let mut buffer = Buffer::empty(area);

                // This should panic because render is not implemented
                chart.render(area, &mut buffer);
            })
            .unwrap();
    }

    #[test]
    fn test_builder_pattern_methods() {
        let data = vec![("A".to_string(), 10), ("B".to_string(), 20)];
        let chart = BarChart::new(data)
            .bar_width(5)
            .bar_gap(2)
            .max_height(15)
            .bar_style(Style::default().fg(Color::Green))
            .value_style(Style::default().fg(Color::Yellow))
            .label_style(Style::default().fg(Color::Cyan));

        assert_eq!(chart.bar_width, 5);
        assert_eq!(chart.bar_gap, 2);
        assert_eq!(chart.max_height, 15);
        assert_eq!(chart.bar_style.fg, Some(Color::Green));
        assert_eq!(chart.value_style.fg, Some(Color::Yellow));
        assert_eq!(chart.label_style.fg, Some(Color::Cyan));
    }

    #[test]
    fn test_empty_data_handling() {
        let empty_data = vec![];
        let highlights = vec![];
        let chart = BarChart::from_array_with_colors(&empty_data, &highlights);

        assert_eq!(chart.data.len(), 0);

        // Should handle scaling gracefully with empty data
        let scaled_chart = chart.scale_for_terminal(80, 20);
        assert!(scaled_chart.bar_width >= 1); // Should maintain minimum width
    }

    #[test]
    fn test_single_element_array() {
        let single_element = vec![42];
        let highlights = vec![];
        let chart = BarChart::from_array_with_colors(&single_element, &highlights);

        assert_eq!(chart.data.len(), 1);
        assert_eq!(chart.data[0], ("0".to_string(), 42));

        // Should scale appropriately for single element
        let scaled_chart = chart.scale_for_terminal(80, 20);
        assert!(scaled_chart.bar_width >= 1);
        assert!(scaled_chart.max_height >= 10);
    }

    #[test]
    fn test_large_values_handling() {
        let large_values = vec![1000000, 2000000, 3000000];
        let highlights = vec![];
        let chart = BarChart::from_array_with_colors(&large_values, &highlights);

        assert_eq!(chart.data.len(), 3);
        assert_eq!(chart.data[0], ("0".to_string(), 1000000));
        assert_eq!(chart.data[2], ("2".to_string(), 3000000));
    }

    #[test]
    fn test_multiple_highlight_types() {
        let array_data = vec![1, 2, 3, 4, 5];

        // Test three highlights (should use default styling)
        let many_highlights = vec![0, 1, 2];
        let chart = BarChart::from_array_with_colors(&array_data, &many_highlights);
        assert_eq!(chart.bar_style.fg, None); // Should use default

        // Test empty highlights
        let no_highlights = vec![];
        let chart_empty = BarChart::from_array_with_colors(&array_data, &no_highlights);
        assert_eq!(chart_empty.bar_style.fg, None);
    }
}