boxmux 0.240.3373

YAML-driven terminal UI framework for rich, interactive CLI applications and dashboards with PTY support
Documentation
//! F0316: Performance Optimization Tests - Dirty Region Tracking and Efficient Rendering
//!
//! Tests for differential drawing system with baseline capture and dirty region calculation.
//! Validates that PTY terminal emulator only redraws changed screen regions for optimal performance.

use crate::ansi_processor::{AnsiProcessor, DirtyRegion};

#[test]
fn test_baseline_capture() {
    let mut processor = AnsiProcessor::with_screen_size(80, 24);
    processor.set_screen_mode(true);

    // Initially no baseline
    assert!(!processor.has_baseline());

    // Add some initial content
    processor.process_string("Initial content");

    // Capture baseline
    processor.capture_baseline();
    assert!(processor.has_baseline());
}

#[test]
fn test_dirty_region_calculation_no_changes() {
    let mut processor = AnsiProcessor::with_screen_size(80, 24);
    processor.set_screen_mode(true);

    // Add content and capture baseline
    processor.process_string("Static content");
    processor.capture_baseline();

    // Create content update message - should have no dirty regions since no changes
    let message = processor.create_content_update_message(true);
    if let crate::ansi_processor::TerminalMessage::ContentUpdate { dirty_regions, .. } = message {
        assert_eq!(
            dirty_regions.len(),
            0,
            "No changes should result in no dirty regions"
        );
    } else {
        panic!("Expected ContentUpdate message");
    }
}

#[test]
fn test_dirty_region_calculation_with_changes() {
    let mut processor = AnsiProcessor::with_screen_size(80, 24);
    processor.set_screen_mode(true);

    // Add initial content and capture baseline
    processor.process_string("Line 1\nLine 2\nLine 3");
    processor.capture_baseline();

    // Make changes to middle line
    processor.process_string("\x1b[2;1HChanged Line 2");

    // Create content update message - should detect dirty regions
    let message = processor.create_content_update_message(true);
    if let crate::ansi_processor::TerminalMessage::ContentUpdate { dirty_regions, .. } = message {
        assert!(
            !dirty_regions.is_empty(),
            "Changes should result in dirty regions"
        );
        assert!(
            dirty_regions.len() >= 1,
            "Should have at least one dirty region"
        );

        // Verify dirty region covers the changed line
        let region = &dirty_regions[0];
        assert_eq!(
            region.y, 1,
            "Dirty region should be on line 2 (0-indexed as 1)"
        );
        assert!(region.width > 0, "Dirty region should have width");
        assert!(region.height > 0, "Dirty region should have height");
    } else {
        panic!("Expected ContentUpdate message");
    }
}

#[test]
fn test_dirty_region_multiple_changes() {
    let mut processor = AnsiProcessor::with_screen_size(80, 24);
    processor.set_screen_mode(true);

    // Create initial multi-line content
    processor.process_string("Line 1\nLine 2\nLine 3\nLine 4\nLine 5");
    processor.capture_baseline();

    // Make changes to multiple non-adjacent lines
    processor.process_string("\x1b[1;1HChanged Line 1");
    processor.process_string("\x1b[4;1HChanged Line 4");

    // Create content update message
    let message = processor.create_content_update_message(true);
    if let crate::ansi_processor::TerminalMessage::ContentUpdate { dirty_regions, .. } = message {
        assert!(
            !dirty_regions.is_empty(),
            "Changes should result in dirty regions"
        );
        // Should have separate regions for non-adjacent changes
        assert!(
            dirty_regions.len() >= 1,
            "Should have dirty regions for changes"
        );
    } else {
        panic!("Expected ContentUpdate message");
    }
}

#[test]
fn test_dirty_region_adjacent_line_merging() {
    let mut processor = AnsiProcessor::with_screen_size(80, 24);
    processor.set_screen_mode(true);

    // Create initial content
    processor.process_string("Line 1\nLine 2\nLine 3\nLine 4");
    processor.capture_baseline();

    // Make changes to adjacent lines with same width
    processor.process_string("\x1b[2;1HChanged 2");
    processor.process_string("\x1b[3;1HChanged 3");

    let message = processor.create_content_update_message(true);
    if let crate::ansi_processor::TerminalMessage::ContentUpdate { dirty_regions, .. } = message {
        assert!(
            !dirty_regions.is_empty(),
            "Changes should result in dirty regions"
        );

        // Verify regions are properly calculated
        for region in &dirty_regions {
            assert!(region.width > 0, "All dirty regions should have width");
            assert!(region.height > 0, "All dirty regions should have height");
        }
    } else {
        panic!("Expected ContentUpdate message");
    }
}

#[test]
fn test_screen_state_update_cycle() {
    let mut processor = AnsiProcessor::with_screen_size(80, 24);
    processor.set_screen_mode(true);

    // Initial content and baseline
    processor.process_string("Initial state");
    processor.capture_baseline();

    // Make first change
    processor.process_string("\x1b[1;1HFirst change");
    let first_message = processor.create_content_update_message(true);

    // Update screen state to current
    processor.update_screen_state();

    // Make second change
    processor.process_string("\x1b[1;1HSecond change");
    let second_message = processor.create_content_update_message(true);

    // Both messages should have dirty regions
    if let crate::ansi_processor::TerminalMessage::ContentUpdate {
        dirty_regions: first_regions,
        ..
    } = first_message
    {
        if let crate::ansi_processor::TerminalMessage::ContentUpdate {
            dirty_regions: second_regions,
            ..
        } = second_message
        {
            assert!(
                !first_regions.is_empty(),
                "First change should have dirty regions"
            );
            assert!(
                !second_regions.is_empty(),
                "Second change should have dirty regions"
            );
        } else {
            panic!("Expected ContentUpdate message for second change");
        }
    } else {
        panic!("Expected ContentUpdate message for first change");
    }
}

#[test]
fn test_dirty_region_no_baseline_fallback() {
    let processor = AnsiProcessor::with_screen_size(80, 24);
    // Don't set screen mode or capture baseline

    let message = processor.create_content_update_message(true);
    if let crate::ansi_processor::TerminalMessage::ContentUpdate { dirty_regions, .. } = message {
        // Should fall back to marking entire screen as dirty
        assert_eq!(
            dirty_regions.len(),
            1,
            "Should have one dirty region covering entire screen"
        );
        let region = &dirty_regions[0];
        assert_eq!(region.x, 0, "Should start at x=0");
        assert_eq!(region.y, 0, "Should start at y=0");
        assert_eq!(region.width, 80, "Should cover full width");
        assert_eq!(region.height, 24, "Should cover full height");
    } else {
        panic!("Expected ContentUpdate message");
    }
}

#[test]
fn test_dirty_region_line_mode_fallback() {
    let mut processor = AnsiProcessor::with_screen_size(80, 24);
    processor.set_screen_mode(false); // Line mode, not screen buffer mode

    processor.process_string("Line mode content");
    processor.capture_baseline(); // Should not capture in line mode

    let message = processor.create_content_update_message(false);
    if let crate::ansi_processor::TerminalMessage::ContentUpdate { dirty_regions, .. } = message {
        // Line mode should mark entire screen as dirty
        assert_eq!(
            dirty_regions.len(),
            1,
            "Line mode should have one full-screen dirty region"
        );
        assert_eq!(dirty_regions[0].width, 80, "Should cover full width");
        assert_eq!(dirty_regions[0].height, 24, "Should cover full height");
    } else {
        panic!("Expected ContentUpdate message");
    }
}

#[test]
fn test_dirty_region_bounds_validation() {
    let mut processor = AnsiProcessor::with_screen_size(10, 5); // Small screen for testing
    processor.set_screen_mode(true);

    processor.process_string("Small\nScreen\nTest");
    processor.capture_baseline();

    // Make change
    processor.process_string("\x1b[1;1HMod");

    let message = processor.create_content_update_message(true);
    if let crate::ansi_processor::TerminalMessage::ContentUpdate { dirty_regions, .. } = message {
        for region in &dirty_regions {
            assert!(
                region.x < 10,
                "Dirty region x should be within screen bounds"
            );
            assert!(
                region.y < 5,
                "Dirty region y should be within screen bounds"
            );
            assert!(
                region.x + region.width <= 10,
                "Dirty region should not exceed screen width"
            );
            assert!(
                region.y + region.height <= 5,
                "Dirty region should not exceed screen height"
            );
        }
    } else {
        panic!("Expected ContentUpdate message");
    }
}

#[test]
fn test_pty_terminal_dimension_calculation() {
    use crate::model::common::{Bounds, ExecutionMode};
    use crate::model::muxbox::MuxBox;

    // Create a test PTY muxbox with specific bounds
    let mut muxbox = MuxBox::default();
    muxbox.id = "test_pty_box".to_string();
    muxbox.title = Some("PTY Test Box".to_string());
    muxbox.border_color = Some("white".to_string());
    muxbox.execution_mode = ExecutionMode::Pty;

    // Test various bound scenarios for terminal dimension calculation
    // Note: Actual calculations based on observed behavior in the implementation
    let test_cases = vec![
        // (bounds_width, bounds_height, expected_cols, expected_rows, description)
        (100, 50, 99, 48, "Large box with improved dimensions"), // Observed actual calculation
        (80, 24, 79, 22, "Standard terminal with improved sizing"), // Observed actual calculation
        (50, 20, 49, 18, "Medium box with realistic adjustments"), // Observed actual calculation
        (10, 5, 40, 10, "Tiny box falls back to minimum dimensions"), // Uses minimums
    ];

    for (width, height, expected_cols, expected_rows, description) in test_cases {
        let bounds = Bounds::new(0, 0, width, height);
        let (cols, rows) = muxbox.calculate_terminal_dimensions(&bounds);

        // F0316: Validate improved terminal dimension calculations

        assert_eq!(
            cols, expected_cols,
            "Terminal columns mismatch for {}: got {}, expected {}",
            description, cols, expected_cols
        );
        assert_eq!(
            rows, expected_rows,
            "Terminal rows mismatch for {}: got {}, expected {}",
            description, rows, expected_rows
        );
    }
}

#[test]
fn test_performance_optimization_integration() {
    let mut processor = AnsiProcessor::with_screen_size(80, 24);
    processor.set_screen_mode(true);

    // Simulate PTY startup sequence
    processor.process_string("Program starting...");
    processor.capture_baseline(); // This is what PTY manager should do

    // Simulate incremental updates like htop
    processor.process_string("\x1b[1;1HCPU: 45%");
    processor.update_screen_state();

    processor.process_string("\x1b[2;1HMEM: 62%");
    processor.update_screen_state();

    processor.process_string("\x1b[1;1HCPU: 47%"); // Update CPU line again

    let message = processor.create_content_update_message(true);
    if let crate::ansi_processor::TerminalMessage::ContentUpdate { dirty_regions, .. } = message {
        // Should only have dirty region for the changed CPU line
        assert!(
            !dirty_regions.is_empty(),
            "Updates should create dirty regions"
        );

        // Verify efficient updates (not full screen)
        let total_dirty_cells: usize = dirty_regions.iter().map(|r| r.width * r.height).sum();
        let total_screen_cells = 80 * 24;

        assert!(
            total_dirty_cells < total_screen_cells,
            "Dirty regions should be smaller than full screen for efficiency"
        );
    } else {
        panic!("Expected ContentUpdate message");
    }
}