boxmux 0.240.3373

YAML-driven terminal UI framework for rich, interactive CLI applications and dashboards with PTY support
Documentation
use crate::model::common::{Bounds, ExecutionMode};
use crate::model::muxbox::MuxBox;
use crate::pty_manager::PtyManager;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_pty_box_dimensions_match_terminal() {
        // F0317: Test that PTY box acts like terminal window - dimensions should match
        let mut muxbox = MuxBox {
            id: "pty_box_1".to_string(),
            execution_mode: ExecutionMode::Pty,
            ..Default::default()
        };

        // Simulate a box with 80x24 character dimensions (typical terminal)
        let bounds = Bounds::new(0, 0, 79, 23); // 80 columns × 24 rows

        // Box should calculate terminal dimensions from its bounds
        let (expected_cols, expected_rows) = muxbox.calculate_terminal_dimensions(&bounds);
        assert_eq!(expected_cols, 80);
        assert_eq!(expected_rows, 24);
    }

    #[test]
    fn test_pty_resize_on_bounds_change() {
        // F0317: Test that PTY resize happens when box bounds change
        let mut pty_manager = PtyManager::new().unwrap();
        let mut muxbox = MuxBox {
            id: "resizable_pty".to_string(),
            execution_mode: ExecutionMode::Pty,
            ..Default::default()
        };

        // Initial bounds (80x24)
        let initial_bounds = Bounds::new(0, 0, 79, 23);
        let (initial_cols, initial_rows) = (80u16, 24u16);

        // Resize to larger bounds (120x40)
        let new_bounds = Bounds::new(0, 0, 119, 39);
        let (new_cols, new_rows) = (120u16, 40u16);

        // Update box bounds should trigger PTY resize
        let result = muxbox.update_bounds_with_pty_resize(&new_bounds, &mut pty_manager);
        assert!(result.is_ok());

        // Verify PTY was resized
        // Note: This test assumes PTY exists - in real usage PTY would be created first
        let resize_result = pty_manager.resize_pty(&muxbox.id, new_rows, new_cols);
        assert!(resize_result.is_ok());
    }

    #[test]
    fn test_non_pty_box_ignores_resize() {
        // F0317: Test that non-PTY boxes don't trigger PTY operations
        let mut pty_manager = PtyManager::new().unwrap();
        let mut muxbox = MuxBox {
            id: "regular_box".to_string(),
            execution_mode: ExecutionMode::Thread, // Not PTY
            ..Default::default()
        };

        let bounds = Bounds::new(0, 0, 99, 29);

        // Regular box bounds update should not attempt PTY resize
        let result = muxbox.update_bounds_with_pty_resize(&bounds, &mut pty_manager);
        assert!(result.is_ok()); // Should succeed but do nothing PTY-related
    }

    #[test]
    fn test_pty_title_propagation() {
        // F0315 + F0317: Test that PTY title changes propagate to MuxBox title
        let mut muxbox = MuxBox {
            id: "titled_pty".to_string(),
            execution_mode: ExecutionMode::Pty,
            title: Some("Original Title".to_string()),
            ..Default::default()
        };

        // Simulate terminal title change from AnsiProcessor
        let terminal_title = "vim - editing file.txt";

        // MuxBox should update its title from PTY terminal title
        muxbox.update_title_from_pty(Some(terminal_title.to_string()));
        assert_eq!(muxbox.title, Some(terminal_title.to_string()));
    }

    #[test]
    fn test_calculate_terminal_dimensions_various_sizes() {
        // F0317: Test terminal dimension calculation for various box sizes
        let muxbox = MuxBox {
            id: "test_box".to_string(),
            execution_mode: ExecutionMode::Pty,
            ..Default::default()
        };

        // Test different terminal sizes
        let test_cases = vec![
            (Bounds::new(0, 0, 79, 23), (80, 24)),   // Standard terminal
            (Bounds::new(0, 0, 119, 39), (120, 40)), // Large terminal
            (Bounds::new(0, 0, 39, 11), (40, 12)),   // Small terminal
            (Bounds::new(0, 0, 199, 59), (200, 60)), // Wide terminal
            (Bounds::new(0, 0, 24, 24), (40, 25)),   // Square terminal (min_cols=40 enforced)
        ];

        for (bounds, (expected_cols, expected_rows)) in test_cases {
            let (cols, rows) = muxbox.calculate_terminal_dimensions(&bounds);
            assert_eq!(cols, expected_cols, "Cols mismatch for bounds {:?}", bounds);
            assert_eq!(rows, expected_rows, "Rows mismatch for bounds {:?}", bounds);
        }
    }

    #[test]
    fn test_pty_ansi_processor_resize_sync() {
        // F0317: Test that AnsiProcessor terminal state resizes with PTY
        use crate::ansi_processor::AnsiProcessor;

        let mut processor = AnsiProcessor::new();

        // Initial size
        assert_eq!(processor.terminal_state.screen_width, 80);
        assert_eq!(processor.terminal_state.screen_height, 24);

        // Resize terminal state to match new PTY bounds
        processor.resize_screen(120, 40);

        // Verify terminal state dimensions updated
        assert_eq!(processor.terminal_state.screen_width, 120);
        assert_eq!(processor.terminal_state.screen_height, 40);

        // Verify tab stops were recalculated
        assert_eq!(processor.terminal_state.tab_stops.len(), 120);
        assert!(processor.terminal_state.tab_stops[8]); // Default tab at column 8
        assert!(processor.terminal_state.tab_stops[16]); // Default tab at column 16
        assert!(processor.terminal_state.tab_stops[112]); // Default tab at column 112
    }

    #[test]
    fn test_bounds_to_character_dimensions() {
        // F0317: Test bounds-to-character conversion logic

        // Assuming monospace font: 1 character = 1 terminal cell
        // Bounds are inclusive, so width = x2 - x1 + 1
        let bounds = Bounds::new(10, 5, 89, 28); // x2-x1+1=80, y2-y1+1=24

        let width = bounds.width();
        let height = bounds.height();

        assert_eq!(width, 80); // 89 - 10 + 1 = 80 characters
        assert_eq!(height, 24); // 28 - 5 + 1 = 24 characters
    }

    #[test]
    fn test_sigwinch_simulation() {
        // F0317: Test PTY resize signals proper terminal resize behavior
        let mut pty_manager = PtyManager::new().unwrap();

        // Simulate SIGWINCH-like resize operation
        let muxbox_id = "sigwinch_test";
        let new_rows = 50u16;
        let new_cols = 132u16;

        // PTY resize should handle SIGWINCH internally
        let result = pty_manager.resize_pty(muxbox_id, new_rows, new_cols);
        assert!(result.is_ok());

        // Note: Real SIGWINCH would be sent to the PTY process automatically by portable_pty
        // This test verifies the resize API works correctly
    }

    #[test]
    fn test_content_preservation_during_resize() {
        // F0317: Test that terminal content is preserved during resize
        use crate::ansi_processor::AnsiProcessor;

        let mut processor = AnsiProcessor::new();

        // Add some content
        processor.process_bytes("Hello\nWorld\nTest\n".as_bytes());
        let original_content = processor.get_processed_text();

        // Resize should preserve existing content
        processor.resize_screen(120, 40);
        let resized_content = processor.get_processed_text();

        // Content should be preserved (though layout may change)
        assert!(resized_content.contains("Hello"));
        assert!(resized_content.contains("World"));
        assert!(resized_content.contains("Test"));
    }

    #[test]
    fn test_cursor_position_adjustment() {
        // F0317: Test that cursor position is properly adjusted during resize
        use crate::ansi_processor::AnsiProcessor;

        let mut processor = AnsiProcessor::new();

        // Position cursor at (70, 20) in 80x24 terminal
        processor.process_bytes("\x1b[21;71H".as_bytes()); // Move to row 21, col 71

        let original_x = processor.get_cursor_x();
        let original_y = processor.get_cursor_y();
        assert_eq!(original_x, 70);
        assert_eq!(original_y, 20);

        // Resize to smaller terminal (40x12)
        processor.resize_screen(40, 12);

        // Cursor should be adjusted to stay within bounds
        let new_x = processor.get_cursor_x();
        let new_y = processor.get_cursor_y();
        assert!(new_x < 40);
        assert!(new_y < 12);
    }

    #[test]
    fn test_line_rewrapping_on_resize() {
        // F0317: Test that long lines are rewrapped when terminal is resized
        use crate::ansi_processor::AnsiProcessor;

        let mut processor = AnsiProcessor::new();

        // Add a long line that will need rewrapping
        let long_line = "This is a very long line that will definitely exceed the terminal width and should be wrapped when the terminal is resized to a smaller width";
        processor.process_bytes(long_line.as_bytes());

        // Resize to smaller width
        processor.resize_screen(40, 24);

        // Content should be rewrapped but preserved
        let content = processor.get_processed_text();
        assert!(content.contains("This is a very long"));
        assert!(content.contains("line that will"));
        // The exact wrapping depends on implementation, but content should be preserved
    }
}