boxmux 0.240.3373

YAML-driven terminal UI framework for rich, interactive CLI applications and dashboards with PTY support
Documentation
//! F0319: Terminal Compatibility Testing - Real terminal applications
//!
//! Tests for terminal compatibility with actual command-line programs.
//! Validates that BoxMux PTY can properly run vi, htop, top, less, and other interactive programs.

use crate::ansi_processor::AnsiProcessor;
use crate::model::app::AppContext;
use crate::model::muxbox::MuxBox;
use crate::pty_manager::PtyManager;
use std::process::Command;
use std::thread;
use std::time::Duration;

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

    #[test]
    fn test_ls_color_output_compatibility() {
        // F0319: Test ls --color=always produces proper ANSI output
        let mut processor = AnsiProcessor::with_screen_size(80, 24);

        // Simulate ls --color output with directory listing and colors
        let ls_output = "\x1b[0m\x1b[01;34mbin\x1b[0m  \x1b[01;34mlib\x1b[0m  \x1b[00msrc\x1b[0m  \x1b[01;32mREADME.md\x1b[0m\n";
        processor.process_bytes(ls_output.as_bytes());

        let content = processor.get_processed_text();

        // Should contain directory names without escape sequences
        assert!(content.contains("bin"));
        assert!(content.contains("lib"));
        assert!(content.contains("src"));
        assert!(content.contains("README.md"));

        // Should not contain raw escape sequences in final output
        assert!(!content.contains("\x1b["));
    }

    #[test]
    fn test_cat_file_output_compatibility() {
        // F0319: Test cat command with various file types
        let mut processor = AnsiProcessor::with_screen_size(80, 24);

        // Simulate cat output with plain text
        let cat_output = "Hello World\nThis is a test file\nWith multiple lines\n";
        processor.process_bytes(cat_output.as_bytes());

        let content = processor.get_processed_text();
        let lines: Vec<&str> = content.split('\n').collect();

        assert_eq!(lines[0], "Hello World");
        assert_eq!(lines[1], "This is a test file");
        assert_eq!(lines[2], "With multiple lines");
    }

    #[test]
    fn test_grep_output_compatibility() {
        // F0319: Test grep output with line numbers and highlighting
        let mut processor = AnsiProcessor::with_screen_size(80, 24);

        // Simulate grep -n --color=always output
        let grep_output = "\x1b[35m\x1b[Kmain.rs\x1b[m\x1b[K:\x1b[36m\x1b[K42\x1b[m\x1b[K:fn \x1b[01;31m\x1b[Kmain\x1b[m\x1b[K() {\n";
        processor.process_bytes(grep_output.as_bytes());

        let content = processor.get_processed_text();

        // Should contain the filename, line number, and function
        assert!(content.contains("main.rs"));
        assert!(content.contains("42"));
        assert!(content.contains("fn main() {"));
    }

    #[test]
    fn test_ps_output_compatibility() {
        // F0319: Test ps command output with process information
        let mut processor = AnsiProcessor::with_screen_size(120, 24);

        // Simulate ps aux output header and sample processes
        let ps_output = "  PID TTY          TIME CMD\n 1234 pts/0    00:00:01 boxmux\n 5678 pts/1    00:00:00 vim\n";
        processor.process_bytes(ps_output.as_bytes());

        let content = processor.get_processed_text();
        let lines: Vec<&str> = content.split('\n').collect();

        // Verify header line
        assert!(lines[0].contains("PID"));
        assert!(lines[0].contains("TTY"));
        assert!(lines[0].contains("CMD"));

        // Verify process entries
        assert!(lines[1].contains("1234"));
        assert!(lines[1].contains("boxmux"));
        assert!(lines[2].contains("5678"));
        assert!(lines[2].contains("vim"));
    }

    #[test]
    fn test_top_like_header_compatibility() {
        // F0319: Test top-like header with system information
        let mut processor = AnsiProcessor::with_screen_size(80, 24);

        // Simulate top header with cursor positioning and updates
        let top_output = "\x1b[H\x1b[2Jtop - 12:34:56 up 1 day,  5:43,  3 users,  load average: 0.45, 0.32, 0.28\n";
        top_output
            .chars()
            .for_each(|c| processor.process_bytes(&[c as u8]));

        let content = processor.get_processed_text();

        // Should contain top header information
        assert!(content.contains("top -"));
        assert!(content.contains("12:34:56"));
        assert!(content.contains("load average"));

        // Cursor should be positioned at top-left after clear screen
        let state = processor.get_terminal_state();
        assert_eq!(state.get_cursor().x, 0);
        assert_eq!(state.get_cursor().y, 1); // After writing the header line
    }

    #[test]
    fn test_vi_status_line_compatibility() {
        // F0319: Test vi-like status line with file information
        let mut processor = AnsiProcessor::with_screen_size(80, 24);

        // Simulate vi status line at bottom of screen
        let vi_output = "\x1b[24;1H\"main.rs\" 42L, 1337C\x1b[6n";
        processor.process_bytes(vi_output.as_bytes());

        // Should handle cursor positioning to last line
        let state = processor.get_terminal_state();
        assert_eq!(state.get_cursor().y, 23); // 24th line (0-indexed = 23)
        assert_eq!(state.get_cursor().x, 20); // After the status text "\"main.rs\" 42L, 1337C"

        let content = processor.get_processed_text();
        // Should contain the status information
        assert!(content.contains("\"main.rs\""));
        assert!(content.contains("42L"));
        assert!(content.contains("1337C"));
    }

    #[test]
    fn test_less_pager_compatibility() {
        // F0319: Test less pager with scroll indicators and content
        let mut processor = AnsiProcessor::with_screen_size(80, 24);

        // Simulate less output with content and bottom status
        processor.process_bytes(b"Line 1\nLine 2\nLine 3\n");
        processor.process_bytes(b"\x1b[24;1H:"); // less prompt at bottom

        let content = processor.get_processed_text();
        let lines: Vec<&str> = content.split('\n').collect();

        // Should contain the file content
        assert!(lines[0].contains("Line 1"));
        assert!(lines[1].contains("Line 2"));
        assert!(lines[2].contains("Line 3"));

        // Status line should be at bottom
        let state = processor.get_terminal_state();
        assert_eq!(state.get_cursor().y, 23); // Bottom line
    }

    #[test]
    fn test_nano_editor_compatibility() {
        // F0319: Test nano editor interface with title and status bars
        let mut processor = AnsiProcessor::with_screen_size(80, 24);

        // Simulate nano interface with title bar and content area
        let nano_output =
            "\x1b[H\x1b[2J  GNU nano 4.8                    main.rs                             \n";
        processor.process_bytes(nano_output.as_bytes());

        let content = processor.get_processed_text();

        // Should contain nano title information
        assert!(content.contains("GNU nano"));
        assert!(content.contains("main.rs"));

        // Cursor should be positioned correctly after clear and title
        let state = processor.get_terminal_state();
        assert_eq!(state.get_cursor().y, 1); // After title line
    }

    #[test]
    fn test_htop_process_list_compatibility() {
        // F0319: Test htop-like process display with colors and formatting
        let mut processor = AnsiProcessor::with_screen_size(120, 30);

        // Simulate htop process list with colored output
        let htop_output = "\x1b[H\x1b[2J\x1b[1;32m  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND\x1b[0m\n";
        htop_output
            .chars()
            .for_each(|c| processor.process_bytes(&[c as u8]));

        // Add sample process entry
        let process_line = "\x1b[33m 1234\x1b[0m bahram    20   0  145768  12234   8956 S   0.3  0.8   0:01.23 \x1b[1mboxmux\x1b[0m\n";
        processor.process_bytes(process_line.as_bytes());

        let content = processor.get_processed_text();

        // Should contain htop header
        assert!(content.contains("PID USER"));
        assert!(content.contains("COMMAND"));

        // Should contain process information
        assert!(content.contains("1234"));
        assert!(content.contains("bahram"));
        assert!(content.contains("boxmux"));
    }

    #[test]
    fn test_terminal_escape_sequence_compatibility() {
        // F0319: Test various terminal escape sequences used by real applications
        let mut processor = AnsiProcessor::with_screen_size(80, 24);

        // Test cursor save/restore sequences
        processor.process_bytes(b"\x1b[s"); // Save cursor
        processor.process_bytes(b"Hello");
        processor.process_bytes(b"\x1b[u"); // Restore cursor
        processor.process_bytes(b"World");

        let content = processor.get_processed_text();

        // After save/restore, "World" should overwrite "Hello"
        assert!(content.contains("World"));

        // Test basic cursor positioning
        processor.process_bytes(b"\x1b[H"); // Home position
        processor.process_bytes(b"Start");
        processor.process_bytes(b"\x1b[2;1H"); // Move to second line
        processor.process_bytes(b"Second");

        let final_content = processor.get_processed_text();
        assert!(final_content.contains("Start"));
        assert!(final_content.contains("Second"));

        // Verify cursor is at correct position
        let state = processor.get_terminal_state();
        assert_eq!(state.get_cursor().y, 1); // Second line (0-indexed)
        assert_eq!(state.get_cursor().x, 6); // After "Second"
    }

    #[test]
    fn test_interactive_program_cursor_handling() {
        // F0319: Test cursor handling for interactive programs
        let mut processor = AnsiProcessor::with_screen_size(80, 24);

        // Simulate interactive program cursor movements
        processor.process_bytes(b"\x1b[H"); // Home position
        processor.process_bytes(b"Menu:");
        processor.process_bytes(b"\x1b[2;5H"); // Move to row 2, col 5
        processor.process_bytes(b"1. Option A");
        processor.process_bytes(b"\x1b[3;5H"); // Move to row 3, col 5
        processor.process_bytes(b"2. Option B");
        processor.process_bytes(b"\x1b[2;3H"); // Position cursor at selection

        let state = processor.get_terminal_state();

        // Cursor should be at row 2, column 3
        assert_eq!(state.get_cursor().y, 1); // 0-indexed
        assert_eq!(state.get_cursor().x, 2); // 0-indexed

        let content = processor.get_processed_text();
        assert!(content.contains("Menu:"));
        assert!(content.contains("1. Option A"));
        assert!(content.contains("2. Option B"));
    }

    fn create_test_pty_muxbox() -> MuxBox {
        let mut muxbox = MuxBox::default();
        muxbox.id = "test_pty".to_string();
        muxbox.title = Some("PTY Test".to_string());
        muxbox.execution_mode = crate::model::common::ExecutionMode::Pty;
        muxbox.script = Some(vec!["echo 'PTY Test'".to_string()]);
        muxbox
    }
}