## Coverage Report

#### Created: 2025-09-08 21:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /home/noah/src/ruchy/src/runtime/dataflow_ui.rs

    Line
    Count
    Source
    1
    //! Terminal UI for dataflow debugger (RUCHY-0818)
    2
    //!
    3
    //! Provides an interactive terminal interface for debugging `DataFrame` pipelines,
    4
    //! displaying breakpoints, materialized data, and execution flow.
    5
    6
    use crate::runtime::dataflow_debugger::{
    7
        DataflowDebugger, PipelineStage, SessionState, StageStatus,
    8
        MaterializedFrame,
    9
    };
    10
    use anyhow::Result;
    11
    use std::collections::HashMap;
    12
    use std::io::{self, Write};
    13
    use std::sync::{Arc, Mutex};
    14
    use std::time::{Duration, Instant};
    15
    16
    /// Terminal UI for dataflow debugging
    17
    pub struct DataflowUI {
    18
        /// Reference to the dataflow debugger
    19
        debugger: Arc<Mutex<DataflowDebugger>>,
    20
    21
        /// UI configuration
    22
        config: UIConfig,
    23
    24
        /// Current display mode
    25
        display_mode: DisplayMode,
    26
    27
        /// Terminal dimensions
    28
        #[allow(dead_code)] // Future feature for responsive UI
    29
        terminal_size: (u16, u16), // (width, height)
    30
    31
        /// UI refresh rate
    32
        refresh_interval: Duration,
    33
    34
        /// Last refresh time
    35
        last_refresh: Instant,
    36
    37
        /// Color support enabled
    38
        colors_enabled: bool,
    39
    }
    40
    41
    /// Configuration for the dataflow UI
    42
    #[derive(Debug, Clone)]
    43
    pub struct UIConfig {
    44
        /// Maximum number of rows to display in data preview
    45
        pub max_preview_rows: usize,
    46
    47
        /// Maximum number of events to show in history
    48
        pub max_history_events: usize,
    49
    50
        /// Enable real-time refresh
    51
        pub auto_refresh: bool,
    52
    53
        /// Refresh interval in milliseconds
    54
        pub refresh_interval_ms: u64,
    55
    56
        /// Show performance metrics
    57
        pub show_metrics: bool,
    58
    59
        /// Enable color output
    60
        pub enable_colors: bool,
    61
    62
        /// Compact display mode
    63
        pub compact_mode: bool,
    64
    }
    65
    66
    /// Display modes for the UI
    67
    #[derive(Debug, Clone, PartialEq, Eq)]
    68
    pub enum DisplayMode {
    69
        /// Pipeline overview with all stages
    70
        Overview,
    71
        /// Detailed stage information
    72
        StageDetail(String),
    73
        /// Breakpoint management
    74
        Breakpoints,
    75
        /// Materialized data viewer
    76
        DataViewer(String),
    77
        /// Performance metrics
    78
        Metrics,
    79
        /// Execution history
    80
        History,
    81
        /// Stage diff comparison
    82
        Diff(String, String),
    83
        /// Help screen
    84
        Help,
    85
    }
    86
    87
    impl Default for UIConfig {
    88
    0
        fn default() -> Self {
    89
    0
            Self {
    90
    0
                max_preview_rows: 20,
    91
    0
                max_history_events: 100,
    92
    0
                auto_refresh: true,
    93
    0
                refresh_interval_ms: 1000,
    94
    0
                show_metrics: true,
    95
    0
                enable_colors: true,
    96
    0
                compact_mode: false,
    97
    0
            }
    98
    0
        }
    99
    }
    100
    101
    impl DataflowUI {
    102
        /// Create a new dataflow UI
    103
    0
        pub fn new(debugger: Arc<Mutex<DataflowDebugger>>, config: UIConfig) -> Self {
    104
    0
            Self {
    105
    0
                debugger,
    106
    0
                config: config.clone(),
    107
    0
                display_mode: DisplayMode::Overview,
    108
    0
                terminal_size: Self::get_terminal_size(),
    109
    0
                refresh_interval: Duration::from_millis(config.refresh_interval_ms),
    110
    0
                last_refresh: Instant::now(),
    111
    0
                colors_enabled: config.enable_colors,
    112
    0
            }
    113
    0
        }
    114
    115
        /// Start the interactive UI loop
    116
    0
        pub fn run_interactive(&mut self) -> Result<()> {
    117
    0
            self.print_header()?;
    118
    0
            self.print_help_hint()?;
    119
    120
            loop {
    121
    0
                self.refresh_display()?;
    122
    123
    0
                if let Some(command) = self.get_user_input()? {
    124
    0
                    match self.handle_command(&command)? {
    125
    0
                        UIAction::Continue => continue,
    126
    0
                        UIAction::Exit => break,
    127
                    }
    128
    0
                }
    129
    130
                // Auto-refresh if enabled
    131
    0
                if self.config.auto_refresh && self.last_refresh.elapsed() >= self.refresh_interval {
    132
    0
                    self.refresh_display()?;
    133
    0
                }
    134
    135
    0
                std::thread::sleep(Duration::from_millis(100));
    136
            }
    137
    138
    0
            Ok(())
    139
    0
        }
    140
    141
        /// Refresh the current display
    142
    0
        pub fn refresh_display(&mut self) -> Result<()> {
    143
    0
            self.clear_screen()?;
    144
    145
    0
            match &self.display_mode {
    146
    0
                DisplayMode::Overview => self.render_overview()?,
    147
    0
                DisplayMode::StageDetail(stage_id) => self.render_stage_detail(stage_id)?,
    148
    0
                DisplayMode::Breakpoints => self.render_breakpoints()?,
    149
    0
                DisplayMode::DataViewer(stage_id) => self.render_data_viewer(stage_id)?,
    150
    0
                DisplayMode::Metrics => self.render_metrics()?,
    151
    0
                DisplayMode::History => self.render_history()?,
    152
    0
                DisplayMode::Diff(stage1, stage2) => self.render_diff(stage1, stage2)?,
    153
    0
                DisplayMode::Help => self.render_help()?,
    154
            }
    155
    156
    0
            self.print_status_bar()?;
    157
    0
            self.print_command_prompt()?;
    158
    159
    0
            self.last_refresh = Instant::now();
    160
    0
            Ok(())
    161
    0
        }
    162
    163
        /// Render pipeline overview
    164
    0
        fn render_overview(&self) -> Result<()> {
    165
    0
            self.print_title("📊 Dataflow Pipeline Overview")?;
    166
    167
    0
            let debugger = self.debugger
    168
    0
                .lock()
    169
    0
                .map_err(|_| anyhow::anyhow!("Failed to acquire debugger lock"))?;
    170
    171
    0
            let session_status = debugger.get_session_status()?;
    172
    0
            self.render_session_info(&session_status)?;
    173
    174
            // In a real implementation, we would get actual pipeline stages
    175
    0
            let sample_stages = self.get_sample_stages();
    176
    177
    0
            println!();
    178
    0
            self.print_section_header("Pipeline Stages")?;
    179
    0
            self.print_separator()?;
    180
    181
    0
            if self.colors_enabled {
    182
    0
                println!("{:<4} {:<20} {:<12} {:<10} {:<15} {:<10}",
    183
    0
                         "#", "Stage Name", "Type", "Status", "Rows", "Time");
    184
    0
            } else {
    185
    0
                println!("{:<4} {:<20} {:<12} {:<10} {:<15} {:<10}",
    186
    0
                         "#", "Stage Name", "Type", "Status", "Rows", "Time");
    187
    0
            }
    188
    189
    0
            self.print_separator()?;
    190
    191
    0
            for (i, stage) in sample_stages.iter().enumerate() {
    192
    0
                let status_color = if self.colors_enabled {
    193
    0
                    match stage.status {
    194
    0
                        StageStatus::Completed => "\x1b[32m", // Green
    195
    0
                        StageStatus::Running => "\x1b[33m",   // Yellow
    196
    0
                        StageStatus::Failed(_) => "\x1b[31m", // Red
    197
    0
                        StageStatus::Paused => "\x1b[36m",    // Cyan
    198
    0
                        _ => "\x1b[37m",                      // White
    199
                    }
    200
                } else {
    201
    0
                    ""
    202
                };
    203
    204
    0
                let reset_color = if self.colors_enabled { "\x1b[0m" } else { "" };
    205
    206
    0
                let time_str = stage.execution_time.map_or_else(|| "-".to_string(), |t| format!("{}ms", t.as_millis()));
    207
    208
    0
                let rows_str = stage.rows_processed.map_or_else(|| "-".to_string(), |r| format!("{r}"));
    209
    210
    0
                println!("{:<4} {:<20} {:<12} {}{:<10}{} {:<15} {:<10}",
    211
    0
                         i + 1,
    212
                         stage.stage_name,
    213
                         stage.stage_type,
    214
                         status_color,
    215
                         stage.status,
    216
                         reset_color,
    217
                         rows_str,
    218
                         time_str);
    219
            }
    220
    221
    0
            Ok(())
    222
    0
        }
    223
    224
        /// Render detailed stage information
    225
    0
        fn render_stage_detail(&self, stage_id: &str) -> Result<()> {
    226
    0
            self.print_title(&format!("🔍 Stage Detail: {stage_id}"))?;
    227
    228
            // In a real implementation, we would get actual stage details
    229
    0
            let stage = self.get_sample_stage(stage_id);
    230
    231
    0
            self.print_section_header("Stage Information")?;
    232
    0
            println!("ID: {}", stage.stage_id);
    233
    0
            println!("Name: {}", stage.stage_name);
    234
    0
            println!("Type: {}", stage.stage_type);
    235
    0
            println!("Status: {}", stage.status);
    236
    237
    0
            if let Some(time) = stage.execution_time {
    238
    0
                println!("Execution Time: {}ms", time.as_millis());
    239
    0
            }
    240
    241
    0
            if let Some(rows) = stage.rows_processed {
    242
    0
                println!("Rows Processed: {rows}");
    243
    0
            }
    244
    245
    0
            if let Some(memory) = stage.memory_usage {
    246
    0
                println!("Memory Usage: {}", self.format_bytes(memory));
    247
    0
            }
    248
    249
            // Show schema information if available
    250
    0
            if let Some(input_schema) = &stage.input_schema {
    251
    0
                println!();
    252
    0
                self.print_section_header("Input Schema")?;
    253
    0
                for col in &input_schema.columns {
    254
    0
                    println!("  {}: {} (nullable: {})", col.name, col.data_type, col.nullable);
    255
    0
                }
    256
    0
            }
    257
    258
    0
            if let Some(output_schema) = &stage.output_schema {
    259
    0
                println!();
    260
    0
                self.print_section_header("Output Schema")?;
    261
    0
                for col in &output_schema.columns {
    262
    0
                    println!("  {}: {} (nullable: {})", col.name, col.data_type, col.nullable);
    263
    0
                }
    264
    0
            }
    265
    266
            // Show metadata
    267
    0
            if !stage.metadata.is_empty() {
    268
    0
                println!();
    269
    0
                self.print_section_header("Metadata")?;
    270
    0
                for (key, value) in &stage.metadata {
    271
    0
                    println!("  {key}: {value}");
    272
    0
                }
    273
    0
            }
    274
    275
    0
            Ok(())
    276
    0
        }
    277
    278
        /// Render breakpoints view
    279
    0
        fn render_breakpoints(&self) -> Result<()> {
    280
    0
            self.print_title("🔴 Breakpoint Management")?;
    281
    282
    0
            println!("Active Breakpoints:");
    283
    0
            self.print_separator()?;
    284
    0
            println!("{:<20} {:<10} {:<15} {:<8}", "Stage ID", "Condition", "Actions", "Hit Count");
    285
    0
            self.print_separator()?;
    286
    287
            // In a real implementation, we would get actual breakpoints
    288
    0
            println!("{:<20} {:<10} {:<15} {:<8}", "load_data", "Always", "Pause,Print", "3");
    289
    0
            println!("{:<20} {:<10} {:<15} {:<8}", "filter_stage", "RowCount>1000", "Materialize", "1");
    290
    291
    0
            println!();
    292
    0
            println!("Commands:");
    293
    0
            println!("  add <stage_id> [condition] - Add breakpoint");
    294
    0
            println!("  remove <stage_id>          - Remove breakpoint");
    295
    0
            println!("  toggle <stage_id>          - Toggle breakpoint");
    296
    0
            println!("  clear                      - Clear all breakpoints");
    297
    298
    0
            Ok(())
    299
    0
        }
    300
    301
        /// Render data viewer
    302
    0
        fn render_data_viewer(&self, stage_id: &str) -> Result<()> {
    303
    0
            self.print_title(&format!("📋 Data Viewer: {stage_id}"))?;
    304
    305
    0
            let _debugger = self.debugger
    306
    0
                .lock()
    307
    0
                .map_err(|_| anyhow::anyhow!("Failed to acquire debugger lock"))?;
    308
    309
            // In a real implementation, we would get actual materialized data
    310
    0
            let sample_data = self.get_sample_materialized_data(stage_id);
    311
    312
    0
            self.print_section_header(&format!("Sample Data ({} rows)", sample_data.total_rows))?;
    313
    314
            // Display schema
    315
    0
            println!("Schema:");
    316
    0
            for col in &sample_data.schema.columns {
    317
    0
                println!("  {}: {}", col.name, col.data_type);
    318
    0
            }
    319
    320
    0
            println!();
    321
    0
            println!("Data Preview (showing {} of {} rows):",
    322
    0
                     sample_data.sample_data.len(), sample_data.total_rows);
    323
    324
    0
            self.print_separator()?;
    325
    326
            // Print column headers
    327
    0
            for col in &sample_data.schema.columns {
    328
    0
                print!("{:<15} ", col.name);
    329
    0
            }
    330
    0
            println!();
    331
    332
    0
            self.print_separator()?;
    333
    334
            // Print data rows
    335
    0
            for row in &sample_data.sample_data {
    336
    0
                for value in &row.values {
    337
    0
                    let value_str = format!("{value}");
    338
    0
                    let truncated = if value_str.len() > 14 {
    339
    0
                        format!("{}...", &value_str[..11])
    340
                    } else {
    341
    0
                        value_str
    342
                    };
    343
    0
                    print!("{truncated:<15} ");
    344
                }
    345
    0
                println!();
    346
            }
    347
    348
    0
            println!();
    349
    0
            println!("Memory Size: {}", self.format_bytes(sample_data.memory_size));
    350
    0
            println!("Materialized: {}", self.format_timestamp(sample_data.timestamp));
    351
    352
    0
            Ok(())
    353
    0
        }
    354
    355
        /// Render performance metrics
    356
    0
        fn render_metrics(&self) -> Result<()> {
    357
    0
            self.print_title("⚡ Performance Metrics")?;
    358
    359
    0
            let debugger = self.debugger
    360
    0
                .lock()
    361
    0
                .map_err(|_| anyhow::anyhow!("Failed to acquire debugger lock"))?;
    362
    363
    0
            let metrics = debugger.get_stage_metrics()?;
    364
    365
    0
            if metrics.is_empty() {
    366
    0
                println!("No performance metrics available yet.");
    367
    0
                return Ok(());
    368
    0
            }
    369
    370
    0
            self.print_section_header("Stage Performance")?;
    371
    0
            self.print_separator()?;
    372
    0
            println!("{:<20} {:<10} {:<12} {:<12} {:<10} {:<10}",
    373
                     "Stage", "Time (ms)", "Memory (MB)", "Input Rows", "Output Rows", "Cache Hit");
    374
    0
            self.print_separator()?;
    375
    376
    0
            for (stage_id, metric) in &metrics {
    377
    0
                let cache_hit = metric.cache_hit_ratio.map_or_else(|| "-".to_string(), |r| format!("{:.1}%", r * 100.0));
    378
    379
    0
                println!("{:<20} {:<10} {:<12} {:<12} {:<10} {:<10}",
    380
                         stage_id,
    381
    0
                         metric.execution_time.as_millis(),
    382
    0
                         metric.peak_memory / (1024 * 1024),
    383
                         metric.input_rows,
    384
                         metric.output_rows,
    385
                         cache_hit);
    386
            }
    387
    388
            // Summary metrics
    389
    0
            let total_time: Duration = metrics.values().map(|m| m.execution_time).sum();
    390
    0
            let total_memory: usize = metrics.values().map(|m| m.peak_memory).sum();
    391
    0
            let total_rows: usize = metrics.values().map(|m| m.output_rows).sum();
    392
    393
    0
            println!();
    394
    0
            self.print_section_header("Summary")?;
    395
    0
            println!("Total Execution Time: {}ms", total_time.as_millis());
    396
    0
            println!("Peak Memory Usage: {}", self.format_bytes(total_memory));
    397
    0
            println!("Total Rows Processed: {total_rows}");
    398
    399
    0
            Ok(())
    400
    0
        }
    401
    402
        /// Render execution history
    403
    0
        fn render_history(&self) -> Result<()> {
    404
    0
            self.print_title("📜 Execution History")?;
    405
    406
    0
            let debugger = self.debugger
    407
    0
                .lock()
    408
    0
                .map_err(|_| anyhow::anyhow!("Failed to acquire debugger lock"))?;
    409
    410
    0
            let history = debugger.get_execution_history()?;
    411
    0
            let recent_events = history.iter().rev().take(self.config.max_history_events);
    412
    413
    0
            self.print_separator()?;
    414
    0
            println!("{:<20} {:<15} {:<20} {:<30}", "Timestamp", "Event Type", "Stage", "Details");
    415
    0
            self.print_separator()?;
    416
    417
    0
            for event in recent_events {
    418
    0
                let timestamp_str = self.format_timestamp(event.timestamp);
    419
    0
                let details = event.data.iter()
    420
    0
                    .map(|(k, v)| format!("{k}:{v}"))
    421
    0
                    .collect::<Vec<_>>()
    422
    0
                    .join(", ");
    423
    424
    0
                println!("{:<20} {:<15} {:<20} {:<30}",
    425
                         timestamp_str,
    426
    0
                         format!("{:?}", event.event_type),
    427
                         event.stage_id,
    428
                         details);
    429
            }
    430
    431
    0
            Ok(())
    432
    0
        }
    433
    434
        /// Render stage diff comparison
    435
    0
        fn render_diff(&self, stage1: &str, stage2: &str) -> Result<()> {
    436
    0
            self.print_title(&format!("🔄 Stage Diff: {stage1} → {stage2}"))?;
    437
    438
    0
            let debugger = self.debugger
    439
    0
                .lock()
    440
    0
                .map_err(|_| anyhow::anyhow!("Failed to acquire debugger lock"))?;
    441
    442
            // In a real implementation, we would compute actual diff
    443
    0
            match debugger.compute_stage_diff(stage1, stage2) {
    444
    0
                Ok(diff) => {
    445
    0
                    self.print_section_header("Diff Summary")?;
    446
    0
                    println!("Row Count Change: {}", diff.row_count_diff);
    447
    0
                    println!("Schema Changed: {}", diff.schema_changed);
    448
    449
    0
                    if !diff.column_changes.is_empty() {
    450
    0
                        println!();
    451
    0
                        self.print_section_header("Column Changes")?;
    452
    0
                        for change in &diff.column_changes {
    453
    0
                            println!("  {change:?}");
    454
    0
                        }
    455
    0
                    }
    456
    457
    0
                    if !diff.data_changes.is_empty() {
    458
    0
                        println!();
    459
    0
                        self.print_section_header("Data Changes")?;
    460
    0
                        for change in &diff.data_changes {
    461
    0
                            println!("  {change:?}");
    462
    0
                        }
    463
    0
                    }
    464
                }
    465
    0
                Err(e) => {
    466
    0
                    println!("Error computing diff: {e}");
    467
    0
                    println!("Make sure both stages have materialized data.");
    468
    0
                }
    469
            }
    470
    471
    0
            Ok(())
    472
    0
        }
    473
    474
        /// Render help screen
    475
    0
        fn render_help(&self) -> Result<()> {
    476
    0
            self.print_title("❓ Dataflow Debugger Help")?;
    477
    478
    0
            println!("Navigation Commands:");
    479
    0
            println!("  overview                    - Show pipeline overview");
    480
    0
            println!("  stage <id>                  - Show stage details");
    481
    0
            println!("  breakpoints                 - Manage breakpoints");
    482
    0
            println!("  data <stage_id>             - View materialized data");
    483
    0
            println!("  metrics                     - Show performance metrics");
    484
    0
            println!("  history                     - Show execution history");
    485
    0
            println!("  diff <stage1> <stage2>      - Compare stages");
    486
    0
            println!("  help                        - Show this help");
    487
    0
            println!("  quit/exit                   - Exit debugger");
    488
    489
    0
            println!();
    490
    0
            println!("Debugging Commands:");
    491
    0
            println!("  materialize <stage_id>      - Materialize stage data");
    492
    0
            println!("  break <stage_id>            - Add breakpoint");
    493
    0
            println!("  continue                    - Continue execution");
    494
    0
            println!("  step                        - Execute next stage");
    495
    0
            println!("  export <format> <path>      - Export debug data");
    496
    497
    0
            println!();
    498
    0
            println!("Display Commands:");
    499
    0
            println!("  refresh                     - Refresh current view");
    500
    0
            println!("  colors on/off               - Toggle color output");
    501
    0
            println!("  compact on/off              - Toggle compact mode");
    502
    503
    0
            Ok(())
    504
    0
        }
    505
    506
        /// Handle user commands
    507
    0
        fn handle_command(&mut self, command: &str) -> Result<UIAction> {
    508
    0
            let parts: Vec<&str> = command.split_whitespace().collect();
    509
    0
            if parts.is_empty() {
    510
    0
                return Ok(UIAction::Continue);
    511
    0
            }
    512
    513
    0
            match parts[0].to_lowercase().as_str() {
    514
    0
                "quit" | "exit" | "q" => Ok(UIAction::Exit),
    515
    0
                "help" | "h" => {
    516
    0
                    self.display_mode = DisplayMode::Help;
    517
    0
                    Ok(UIAction::Continue)
    518
                }
    519
    0
                "overview" | "o" => {
    520
    0
                    self.display_mode = DisplayMode::Overview;
    521
    0
                    Ok(UIAction::Continue)
    522
                }
    523
    0
                "stage" | "s" => {
    524
    0
                    if parts.len() > 1 {
    525
    0
                        self.display_mode = DisplayMode::StageDetail(parts[1].to_string());
    526
    0
                    } else {
    527
    0
                        println!("Usage: stage <stage_id>");
    528
    0
                    }
    529
    0
                    Ok(UIAction::Continue)
    530
                }
    531
    0
                "breakpoints" | "bp" => {
    532
    0
                    self.display_mode = DisplayMode::Breakpoints;
    533
    0
                    Ok(UIAction::Continue)
    534
                }
    535
    0
                "data" | "d" => {
    536
    0
                    if parts.len() > 1 {
    537
    0
                        self.display_mode = DisplayMode::DataViewer(parts[1].to_string());
    538
    0
                    } else {
    539
    0
                        println!("Usage: data <stage_id>");
    540
    0
                    }
    541
    0
                    Ok(UIAction::Continue)
    542
                }
    543
    0
                "metrics" | "m" => {
    544
    0
                    self.display_mode = DisplayMode::Metrics;
    545
    0
                    Ok(UIAction::Continue)
    546
                }
    547
    0
                "history" | "hist" => {
    548
    0
                    self.display_mode = DisplayMode::History;
    549
    0
                    Ok(UIAction::Continue)
    550
                }
    551
    0
                "diff" => {
    552
    0
                    if parts.len() > 2 {
    553
    0
                        self.display_mode = DisplayMode::Diff(parts[1].to_string(), parts[2].to_string());
    554
    0
                    } else {
    555
    0
                        println!("Usage: diff <stage1> <stage2>");
    556
    0
                    }
    557
    0
                    Ok(UIAction::Continue)
    558
                }
    559
    0
                "refresh" | "r" => {
    560
                    // Force refresh on next loop
    561
    0
                    self.last_refresh = Instant::now().checked_sub(self.refresh_interval).unwrap();
    562
    0
                    Ok(UIAction::Continue)
    563
                }
    564
    0
                "colors" => {
    565
    0
                    if parts.len() > 1 {
    566
    0
                        match parts[1] {
    567
    0
                            "on" => self.colors_enabled = true,
    568
    0
                            "off" => self.colors_enabled = false,
    569
    0
                            _ => println!("Usage: colors on/off"),
    570
                        }
    571
    0
                    }
    572
    0
                    Ok(UIAction::Continue)
    573
                }
    574
    0
                "materialize" => {
    575
    0
                    if parts.len() > 1 {
    576
    0
                        let debugger = self.debugger
    577
    0
                            .lock()
    578
    0
                            .map_err(|_| anyhow::anyhow!("Failed to acquire debugger lock"))?;
    579
    0
                        match debugger.materialize_stage(parts[1]) {
    580
    0
                            Ok(_) => println!("Materialized data for stage: {}", parts[1]),
    581
    0
                            Err(e) => println!("Failed to materialize: {e}"),
    582
                        }
    583
    0
                    } else {
    584
    0
                        println!("Usage: materialize <stage_id>");
    585
    0
                    }
    586
    0
                    Ok(UIAction::Continue)
    587
                }
    588
                _ => {
    589
    0
                    println!("Unknown command: {}. Type 'help' for available commands.", parts[0]);
    590
    0
                    Ok(UIAction::Continue)
    591
                }
    592
            }
    593
    0
        }
    594
    595
        /// Get user input
    596
    0
        fn get_user_input(&self) -> Result<Option<String>> {
    597
    0
            print!("> ");
    598
    0
            io::stdout().flush()?;
    599
    600
    0
            let mut input = String::new();
    601
    0
            match io::stdin().read_line(&mut input) {
    602
    0
                Ok(0) => Ok(None), // EOF
    603
    0
                Ok(_) => Ok(Some(input.trim().to_string())),
    604
    0
                Err(e) => Err(anyhow::anyhow!("Failed to read input: {e}")),
    605
            }
    606
    0
        }
    607
    608
        // Helper methods for UI rendering
    609
    610
    0
        fn clear_screen(&self) -> Result<()> {
    611
    0
            print!("\x1b[2J\x1b[H");
    612
    0
            io::stdout().flush()?;
    613
    0
            Ok(())
    614
    0
        }
    615
    616
    0
        fn print_header(&self) -> Result<()> {
    617
    0
            if self.colors_enabled {
    618
    0
                println!("\x1b[1;34m╔══════════════════════════════════════════════════════════════════════════════╗\x1b[0m");
    619
    0
                println!("\x1b[1;34m║                        RUCHY DATAFLOW DEBUGGER                              ║\x1b[0m");
    620
    0
                println!("\x1b[1;34m╚══════════════════════════════════════════════════════════════════════════════╝\x1b[0m");
    621
    0
            } else {
    622
    0
                println!("===============================================================================");
    623
    0
                println!("                        RUCHY DATAFLOW DEBUGGER                              ");
    624
    0
                println!("===============================================================================");
    625
    0
            }
    626
    0
            Ok(())
    627
    0
        }
    628
    629
    0
        fn print_title(&self, title: &str) -> Result<()> {
    630
    0
            println!();
    631
    0
            if self.colors_enabled {
    632
    0
                println!("\x1b[1;36m{title}\x1b[0m");
    633
    0
            } else {
    634
    0
                println!("{title}");
    635
    0
            }
    636
    0
            println!();
    637
    0
            Ok(())
    638
    0
        }
    639
    640
    0
        fn print_section_header(&self, header: &str) -> Result<()> {
    641
    0
            if self.colors_enabled {
    642
    0
                println!("\x1b[1;33m{header}:\x1b[0m");
    643
    0
            } else {
    644
    0
                println!("{header}:");
    645
    0
            }
    646
    0
            Ok(())
    647
    0
        }
    648
    649
    0
        fn print_separator(&self) -> Result<()> {
    650
    0
            println!("{}", "-".repeat(80));
    651
    0
            Ok(())
    652
    0
        }
    653
    654
    0
        fn print_help_hint(&self) -> Result<()> {
    655
    0
            println!("Type 'help' for commands, 'quit' to exit");
    656
    0
            println!();
    657
    0
            Ok(())
    658
    0
        }
    659
    660
    0
        fn print_status_bar(&self) -> Result<()> {
    661
    0
            let status = format!("Mode: {:?} | Auto-refresh: {} | Colors: {}",
    662
                               self.display_mode,
    663
                               self.config.auto_refresh,
    664
                               self.colors_enabled);
    665
    666
    0
            if self.colors_enabled {
    667
    0
                println!("\n\x1b[7m{status:<80}\x1b[0m");
    668
    0
            } else {
    669
    0
                println!("\n{status}");
    670
    0
            }
    671
    0
            Ok(())
    672
    0
        }
    673
    674
    0
        fn print_command_prompt(&self) -> Result<()> {
    675
    0
            println!();
    676
    0
            Ok(())
    677
    0
        }
    678
    679
    0
        fn get_terminal_size() -> (u16, u16) {
    680
            // In a real implementation, would detect actual terminal size
    681
    0
            (80, 24)
    682
    0
        }
    683
    684
    0
        fn format_bytes(&self, bytes: usize) -> String {
    685
            const UNITS: &[&str] = &["B", "KB", "MB", "GB"];
    686
    0
            let mut size = bytes as f64;
    687
    0
            let mut unit_index = 0;
    688
    689
    0
            while size >= 1024.0 && unit_index < UNITS.len() - 1 {
    690
    0
                size /= 1024.0;
    691
    0
                unit_index += 1;
    692
    0
            }
    693
    694
    0
            if size >= 100.0 {
    695
    0
                format!("{:.0}{}", size, UNITS[unit_index])
    696
    0
            } else if size >= 10.0 {
    697
    0
                format!("{:.1}{}", size, UNITS[unit_index])
    698
            } else {
    699
    0
                format!("{:.2}{}", size, UNITS[unit_index])
    700
            }
    701
    0
        }
    702
    703
    0
        fn format_timestamp(&self, timestamp: std::time::SystemTime) -> String {
    704
            // Simplified timestamp formatting
    705
    0
            format!("{:?}", timestamp.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs())
    706
    0
        }
    707
    708
    0
        fn render_session_info(&self, session: &SessionState) -> Result<()> {
    709
    0
            self.print_section_header("Session Status")?;
    710
    0
            println!("Active: {}", session.active);
    711
    0
            if let Some(current) = &session.current_stage {
    712
    0
                println!("Current Stage: {current}");
    713
    0
            }
    714
    0
            println!("Total Time: {}ms", session.total_execution_time.as_millis());
    715
    0
            println!("Breakpoints Hit: {}", session.breakpoints_hit);
    716
    0
            Ok(())
    717
    0
        }
    718
    719
        // Sample data methods (in real implementation, these would come from actual debugger state)
    720
    721
    0
        fn get_sample_stages(&self) -> Vec<PipelineStage> {
    722
            use crate::runtime::dataflow_debugger::{StageType, StageStatus};
    723
    724
    0
            vec![
    725
    0
                PipelineStage {
    726
    0
                    stage_id: "load_data".to_string(),
    727
    0
                    stage_name: "Load CSV Data".to_string(),
    728
    0
                    stage_type: StageType::Load,
    729
    0
                    status: StageStatus::Completed,
    730
    0
                    input_schema: None,
    731
    0
                    output_schema: None,
    732
    0
                    execution_time: Some(Duration::from_millis(120)),
    733
    0
                    memory_usage: Some(1024 * 1024 * 5), // 5MB
    734
    0
                    rows_processed: Some(10000),
    735
    0
                    metadata: HashMap::new(),
    736
    0
                },
    737
    0
                PipelineStage {
    738
    0
                    stage_id: "filter_age".to_string(),
    739
    0
                    stage_name: "Filter by Age".to_string(),
    740
    0
                    stage_type: StageType::Filter,
    741
    0
                    status: StageStatus::Completed,
    742
    0
                    input_schema: None,
    743
    0
                    output_schema: None,
    744
    0
                    execution_time: Some(Duration::from_millis(45)),
    745
    0
                    memory_usage: Some(1024 * 1024 * 3), // 3MB
    746
    0
                    rows_processed: Some(7500),
    747
    0
                    metadata: HashMap::new(),
    748
    0
                },
    749
    0
                PipelineStage {
    750
    0
                    stage_id: "group_by_city".to_string(),
    751
    0
                    stage_name: "Group by City".to_string(),
    752
    0
                    stage_type: StageType::GroupBy,
    753
    0
                    status: StageStatus::Running,
    754
    0
                    input_schema: None,
    755
    0
                    output_schema: None,
    756
    0
                    execution_time: None,
    757
    0
                    memory_usage: None,
    758
    0
                    rows_processed: None,
    759
    0
                    metadata: HashMap::new(),
    760
    0
                },
    761
            ]
    762
    0
        }
    763
    764
    0
        fn get_sample_stage(&self, _stage_id: &str) -> PipelineStage {
    765
            use crate::runtime::dataflow_debugger::{StageType, StageStatus, DataSchema, ColumnDef, DataType};
    766
    767
    0
            PipelineStage {
    768
    0
                stage_id: "load_data".to_string(),
    769
    0
                stage_name: "Load CSV Data".to_string(),
    770
    0
                stage_type: StageType::Load,
    771
    0
                status: StageStatus::Completed,
    772
    0
                input_schema: None,
    773
    0
                output_schema: Some(DataSchema {
    774
    0
                    columns: vec![
    775
    0
                        ColumnDef {
    776
    0
                            name: "id".to_string(),
    777
    0
                            data_type: DataType::Integer,
    778
    0
                            nullable: false,
    779
    0
                        },
    780
    0
                        ColumnDef {
    781
    0
                            name: "name".to_string(),
    782
    0
                            data_type: DataType::String,
    783
    0
                            nullable: false,
    784
    0
                        },
    785
    0
                        ColumnDef {
    786
    0
                            name: "age".to_string(),
    787
    0
                            data_type: DataType::Integer,
    788
    0
                            nullable: false,
    789
    0
                        },
    790
    0
                        ColumnDef {
    791
    0
                            name: "city".to_string(),
    792
    0
                            data_type: DataType::String,
    793
    0
                            nullable: true,
    794
    0
                        },
    795
    0
                    ],
    796
    0
                    schema_hash: 12345,
    797
    0
                }),
    798
    0
                execution_time: Some(Duration::from_millis(120)),
    799
    0
                memory_usage: Some(1024 * 1024 * 5), // 5MB
    800
    0
                rows_processed: Some(10000),
    801
    0
                metadata: HashMap::from([
    802
    0
                    ("file_path".to_string(), "/data/users.csv".to_string()),
    803
    0
                    ("encoding".to_string(), "UTF-8".to_string()),
    804
    0
                ]),
    805
    0
            }
    806
    0
        }
    807
    808
    0
        fn get_sample_materialized_data(&self, _stage_id: &str) -> MaterializedFrame {
    809
            use crate::runtime::dataflow_debugger::{
    810
                MaterializedFrame, DataSchema, ColumnDef, DataType, DataRow, DataValue
    811
            };
    812
    813
    0
            MaterializedFrame {
    814
    0
                stage_id: "load_data".to_string(),
    815
    0
                schema: DataSchema {
    816
    0
                    columns: vec![
    817
    0
                        ColumnDef {
    818
    0
                            name: "id".to_string(),
    819
    0
                            data_type: DataType::Integer,
    820
    0
                            nullable: false,
    821
    0
                        },
    822
    0
                        ColumnDef {
    823
    0
                            name: "name".to_string(),
    824
    0
                            data_type: DataType::String,
    825
    0
                            nullable: false,
    826
    0
                        },
    827
    0
                        ColumnDef {
    828
    0
                            name: "age".to_string(),
    829
    0
                            data_type: DataType::Integer,
    830
    0
                            nullable: false,
    831
    0
                        },
    832
    0
                    ],
    833
    0
                    schema_hash: 12345,
    834
    0
                },
    835
    0
                sample_data: vec![
    836
    0
                    DataRow {
    837
    0
                        values: vec![
    838
    0
                            DataValue::Integer(1),
    839
    0
                            DataValue::String("Alice".to_string()),
    840
    0
                            DataValue::Integer(30),
    841
    0
                        ],
    842
    0
                    },
    843
    0
                    DataRow {
    844
    0
                        values: vec![
    845
    0
                            DataValue::Integer(2),
    846
    0
                            DataValue::String("Bob".to_string()),
    847
    0
                            DataValue::Integer(25),
    848
    0
                        ],
    849
    0
                    },
    850
    0
                    DataRow {
    851
    0
                        values: vec![
    852
    0
                            DataValue::Integer(3),
    853
    0
                            DataValue::String("Charlie".to_string()),
    854
    0
                            DataValue::Integer(35),
    855
    0
                        ],
    856
    0
                    },
    857
    0
                ],
    858
    0
                total_rows: 10000,
    859
    0
                timestamp: std::time::SystemTime::now(),
    860
    0
                memory_size: 1024 * 50, // 50KB
    861
    0
            }
    862
    0
        }
    863
    }
    864
    865
    /// UI action results
    866
    #[derive(Debug, Clone, PartialEq, Eq)]
    867
    pub enum UIAction {
    868
        /// Continue UI loop
    869
        Continue,
    870
        /// Exit UI
    871
        Exit,
    872
    }
    873
    874
    #[cfg(test)]
    875
    mod tests {
    876
        use super::*;
    877
        use crate::runtime::dataflow_debugger::{
    878
            DataflowDebugger, DataflowConfig
    879
        };
    880
        use std::sync::{Arc, Mutex};
    881
        use std::time::{Duration, Instant};
    882
    883
        // Helper functions for consistent test setup
    884
        fn create_test_debugger() -> Arc<Mutex<DataflowDebugger>> {
    885
            let config = DataflowConfig::default();
    886
            Arc::new(Mutex::new(DataflowDebugger::new(config)))
    887
        }
    888
    889
        fn create_test_ui_config() -> UIConfig {
    890
            UIConfig {
    891
                max_preview_rows: 5,
    892
                max_history_events: 10,
    893
                auto_refresh: false,
    894
                refresh_interval_ms: 500,
    895
                show_metrics: true,
    896
                enable_colors: false, // Disable for consistent testing
    897
                compact_mode: true,
    898
            }
    899
        }
    900
    901
        fn create_test_ui_with_config(config: UIConfig) -> DataflowUI {
    902
            let debugger = create_test_debugger();
    903
            DataflowUI::new(debugger, config)
    904
        }
    905
    906
        fn create_test_ui() -> DataflowUI {
    907
            create_test_ui_with_config(create_test_ui_config())
    908
        }
    909
    910
        fn create_test_pipeline_stage() -> PipelineStage {
    911
            use crate::runtime::dataflow_debugger::{StageType, StageStatus};
    912
    913
            PipelineStage {
    914
                stage_id: "test_stage".to_string(),
    915
                stage_name: "Test Stage".to_string(),
    916
                stage_type: StageType::Filter,
    917
                status: StageStatus::Running,
    918
                input_schema: None,
    919
                output_schema: None,
    920
                execution_time: Some(Duration::from_millis(150)),
    921
                memory_usage: Some(1024 * 64), // 64KB
    922
                rows_processed: Some(500),
    923
                metadata: std::collections::HashMap::new(),
    924
            }
    925
        }
    926
    927
        fn create_test_materialized_frame() -> MaterializedFrame {
    928
            use crate::runtime::dataflow_debugger::{DataSchema, ColumnDef, DataType, DataRow, DataValue};
    929
            use std::time::SystemTime;
    930
    931
            MaterializedFrame {
    932
                stage_id: "test_stage".to_string(),
    933
                schema: DataSchema {
    934
                    columns: vec![
    935
                        ColumnDef {
    936
                            name: "id".to_string(),
    937
                            data_type: DataType::Integer,
    938
                            nullable: false,
    939
                        },
    940
                        ColumnDef {
    941
                            name: "name".to_string(),
    942
                            data_type: DataType::String,
    943
                            nullable: false,
    944
                        },
    945
                    ],
    946
                    schema_hash: 54321,
    947
                },
    948
                sample_data: vec![
    949
                    DataRow {
    950
                        values: vec![
    951
                            DataValue::Integer(1),
    952
                            DataValue::String("Test User".to_string()),
    953
                        ],
    954
                    },
    955
                ],
    956
                total_rows: 100,
    957
                timestamp: SystemTime::now(),
    958
                memory_size: 1024 * 8, // 8KB
    959
            }
    960
        }
    961
    962
        // ========== UIConfig Tests ==========
    963
    964
        #[test]
    965
        fn test_ui_config_default() {
    966
            let config = UIConfig::default();
    967
            assert_eq!(config.max_preview_rows, 20);
    968
            assert_eq!(config.max_history_events, 100);
    969
            assert!(config.auto_refresh);
    970
            assert_eq!(config.refresh_interval_ms, 1000);
    971
            assert!(config.show_metrics);
    972
            assert!(config.enable_colors);
    973
            assert!(!config.compact_mode);
    974
        }
    975
    976
        #[test]
    977
        fn test_ui_config_clone() {
    978
            let config1 = UIConfig::default();
    979
            let config2 = config1.clone();
    980
            assert_eq!(config1.max_preview_rows, config2.max_preview_rows);
    981
            assert_eq!(config1.auto_refresh, config2.auto_refresh);
    982
        }
    983
    984
        #[test]
    985
        fn test_ui_config_debug() {
    986
            let config = UIConfig::default();
    987
            let debug_str = format!("{config:?}");
    988
            assert!(debug_str.contains("UIConfig"));
    989
            assert!(debug_str.contains("max_preview_rows"));
    990
            assert!(debug_str.contains("auto_refresh"));
    991
        }
    992
    993
        // ========== DisplayMode Tests ==========
    994
    995
        #[test]
    996
        fn test_display_mode_variants() {
    997
            let modes = vec![
    998
                DisplayMode::Overview,
    999
                DisplayMode::StageDetail("test_stage".to_string()),
    1000
                DisplayMode::Breakpoints,
    1001
                DisplayMode::DataViewer("test_data".to_string()),
    1002
                DisplayMode::Metrics,
    1003
                DisplayMode::History,
    1004
                DisplayMode::Diff("stage1".to_string(), "stage2".to_string()),
    1005
                DisplayMode::Help,
    1006
            ];
    1007
    1008
            assert_eq!(modes.len(), 8);
    1009
            assert_eq!(modes[0], DisplayMode::Overview);
    1010
        }
    1011
    1012
        #[test]
    1013
        fn test_display_mode_equality() {
    1014
            let mode1 = DisplayMode::StageDetail("test".to_string());
    1015
            let mode2 = DisplayMode::StageDetail("test".to_string());
    1016
            let mode3 = DisplayMode::StageDetail("other".to_string());
    1017
    1018
            assert_eq!(mode1, mode2);
    1019
            assert_ne!(mode1, mode3);
    1020
            assert_ne!(mode1, DisplayMode::Overview);
    1021
        }
    1022
    1023
        #[test]
    1024
        fn test_display_mode_clone() {
    1025
            let mode1 = DisplayMode::Diff("a".to_string(), "b".to_string());
    1026
            let mode2 = mode1.clone();
    1027
            assert_eq!(mode1, mode2);
    1028
        }
    1029
    1030
        #[test]
    1031
        fn test_display_mode_debug() {
    1032
            let mode = DisplayMode::DataViewer("test_viewer".to_string());
    1033
            let debug_str = format!("{mode:?}");
    1034
            assert!(debug_str.contains("DataViewer"));
    1035
            assert!(debug_str.contains("test_viewer"));
    1036
        }
    1037
    1038
        // ========== UIAction Tests ==========
    1039
    1040
        #[test]
    1041
        fn test_ui_action_variants() {
    1042
            let actions = [UIAction::Continue, UIAction::Exit];
    1043
            assert_eq!(actions.len(), 2);
    1044
            assert_eq!(actions[0], UIAction::Continue);
    1045
            assert_eq!(actions[1], UIAction::Exit);
    1046
        }
    1047
    1048
        #[test]
    1049
        fn test_ui_action_equality() {
    1050
            assert_eq!(UIAction::Continue, UIAction::Continue);
    1051
            assert_eq!(UIAction::Exit, UIAction::Exit);
    1052
            assert_ne!(UIAction::Continue, UIAction::Exit);
    1053
        }
    1054
    1055
        #[test]
    1056
        fn test_ui_action_clone_debug() {
    1057
            let action = UIAction::Continue;
    1058
            let cloned = action.clone();
    1059
            assert_eq!(action, cloned);
    1060
    1061
            let debug_str = format!("{action:?}");
    1062
            assert!(debug_str.contains("Continue"));
    1063
        }
    1064
    1065
        // ========== DataflowUI Creation Tests ==========
    1066
    1067
        #[test]
    1068
        fn test_dataflow_ui_creation() {
    1069
            let debugger = create_test_debugger();
    1070
            let config = create_test_ui_config();
    1071
            let ui = DataflowUI::new(debugger, config.clone());
    1072
    1073
            assert_eq!(ui.display_mode, DisplayMode::Overview);
    1074
            assert_eq!(ui.config.max_preview_rows, config.max_preview_rows);
    1075
            assert!(!ui.colors_enabled);
    1076
        }
    1077
    1078
        #[test]
    1079
        fn test_dataflow_ui_with_default_config() {
    1080
            let debugger = create_test_debugger();
    1081
            let ui = DataflowUI::new(debugger, UIConfig::default());
    1082
    1083
            assert_eq!(ui.display_mode, DisplayMode::Overview);
    1084
            assert_eq!(ui.config.max_preview_rows, 20);
    1085
            assert!(ui.colors_enabled);
    1086
        }
    1087
    1088
        #[test]
    1089
        fn test_dataflow_ui_terminal_size() {
    1090
            let ui = create_test_ui();
    1091
            // Terminal size should be initialized
    1092
            assert!(ui.terminal_size.0 > 0);
    1093
            assert!(ui.terminal_size.1 > 0);
    1094
        }
    1095
    1096
        #[test]
    1097
        fn test_dataflow_ui_refresh_timing() {
    1098
            let ui = create_test_ui();
    1099
            let now = Instant::now();
    1100
            // Last refresh should be recent
    1101
            assert!(now.duration_since(ui.last_refresh) < Duration::from_secs(1));
    1102
        }
    1103
    1104
        // ========== Display Mode Management Tests ==========
    1105
    1106
        #[test]
    1107
        fn test_set_display_mode() {
    1108
            let mut ui = create_test_ui();
    1109
            assert_eq!(ui.display_mode, DisplayMode::Overview);
    1110
    1111
            ui.display_mode = DisplayMode::Metrics;
    1112
            assert_eq!(ui.display_mode, DisplayMode::Metrics);
    1113
    1114
            ui.display_mode = DisplayMode::StageDetail("test".to_string());
    1115
            assert_eq!(ui.display_mode, DisplayMode::StageDetail("test".to_string()));
    1116
        }
    1117
    1118
        #[test]
    1119
        fn test_get_current_display_mode() {
    1120
            let mut ui = create_test_ui();
    1121
            assert_eq!(ui.display_mode, DisplayMode::Overview);
    1122
    1123
            ui.display_mode = DisplayMode::Help;
    1124
            assert_eq!(ui.display_mode, DisplayMode::Help);
    1125
        }
    1126
    1127
        #[test]
    1128
        fn test_toggle_colors() {
    1129
            let mut ui = create_test_ui();
    1130
            let initial_colors = ui.colors_enabled;
    1131
    1132
            ui.colors_enabled = !ui.colors_enabled;
    1133
            assert_eq!(ui.colors_enabled, !initial_colors);
    1134
    1135
            ui.colors_enabled = !ui.colors_enabled;
    1136
            assert_eq!(ui.colors_enabled, initial_colors);
    1137
        }
    1138
    1139
        // ========== Display Rendering Tests ==========
    1140
    1141
        #[test]
    1142
        fn test_render_overview() {
    1143
            let ui = create_test_ui();
    1144
            let result = ui.render_overview();
    1145
            assert!(result.is_ok());
    1146
        }
    1147
    1148
        #[test]
    1149
        fn test_render_stage_detail() {
    1150
            let ui = create_test_ui();
    1151
            let stage_id = "test_stage";
    1152
            let result = ui.render_stage_detail(stage_id);
    1153
            assert!(result.is_ok());
    1154
        }
    1155
    1156
        #[test]
    1157
        fn test_render_breakpoints() {
    1158
            let ui = create_test_ui();
    1159
            let result = ui.render_breakpoints();
    1160
            assert!(result.is_ok());
    1161
        }
    1162
    1163
        #[test]
    1164
        fn test_render_data_viewer() {
    1165
            let ui = create_test_ui();
    1166
            let stage_id = "test_stage";
    1167
            let result = ui.render_data_viewer(stage_id);
    1168
            assert!(result.is_ok());
    1169
        }
    1170
    1171
        #[test]
    1172
        fn test_render_metrics() {
    1173
            let ui = create_test_ui();
    1174
            let result = ui.render_metrics();
    1175
            assert!(result.is_ok());
    1176
        }
    1177
    1178
        #[test]
    1179
        fn test_render_history() {
    1180
            let ui = create_test_ui();
    1181
            let result = ui.render_history();
    1182
            assert!(result.is_ok());
    1183
        }
    1184
    1185
        #[test]
    1186
        fn test_render_diff() {
    1187
            let ui = create_test_ui();
    1188
            let stage1 = "stage_a";
    1189
            let stage2 = "stage_b";
    1190
            let result = ui.render_diff(stage1, stage2);
    1191
            assert!(result.is_ok());
    1192
        }
    1193
    1194
        #[test]
    1195
        fn test_render_help() {
    1196
            let ui = create_test_ui();
    1197
            let result = ui.render_help();
    1198
            assert!(result.is_ok());
    1199
        }
    1200
    1201
        // ========== Input Handling Tests ==========
    1202
    1203
        #[test]
    1204
        fn test_handle_command_navigation() {
    1205
            let mut ui = create_test_ui();
    1206
    1207
            // Test overview mode navigation
    1208
            assert_eq!(ui.handle_command("overview").unwrap(), UIAction::Continue);
    1209
            assert_eq!(ui.display_mode, DisplayMode::Overview);
    1210
    1211
            assert_eq!(ui.handle_command("metrics").unwrap(), UIAction::Continue);
    1212
            assert_eq!(ui.display_mode, DisplayMode::Metrics);
    1213
    1214
            assert_eq!(ui.handle_command("history").unwrap(), UIAction::Continue);
    1215
            assert_eq!(ui.display_mode, DisplayMode::History);
    1216
    1217
            assert_eq!(ui.handle_command("help").unwrap(), UIAction::Continue);
    1218
            assert_eq!(ui.display_mode, DisplayMode::Help);
    1219
        }
    1220
    1221
        #[test]
    1222
        fn test_handle_command_quit() {
    1223
            let mut ui = create_test_ui();
    1224
            assert_eq!(ui.handle_command("quit").unwrap(), UIAction::Exit);
    1225
            assert_eq!(ui.handle_command("exit").unwrap(), UIAction::Exit);
    1226
            assert_eq!(ui.handle_command("q").unwrap(), UIAction::Exit);
    1227
        }
    1228
    1229
        #[test]
    1230
        fn test_handle_command_breakpoints() {
    1231
            let mut ui = create_test_ui();
    1232
            assert_eq!(ui.handle_command("breakpoints").unwrap(), UIAction::Continue);
    1233
            assert_eq!(ui.display_mode, DisplayMode::Breakpoints);
    1234
        }
    1235
    1236
        #[test]
    1237
        fn test_handle_command_colors() {
    1238
            let mut ui = create_test_ui();
    1239
    1240
            ui.handle_command("colors on").unwrap();
    1241
            assert!(ui.colors_enabled);
    1242
    1243
            ui.handle_command("colors off").unwrap();
    1244
            assert!(!ui.colors_enabled);
    1245
        }
    1246
    1247
        #[test]
    1248
        fn test_handle_command_refresh() {
    1249
            let mut ui = create_test_ui();
    1250
    1251
            // Small delay to ensure time difference
    1252
            std::thread::sleep(Duration::from_millis(1));
    1253
    1254
            assert_eq!(ui.handle_command("refresh").unwrap(), UIAction::Continue);
    1255
            // The refresh command updates last_refresh to trigger a refresh
    1256
        }
    1257
    1258
        #[test]
    1259
        fn test_handle_command_unknown() {
    1260
            let mut ui = create_test_ui();
    1261
            let initial_mode = ui.display_mode.clone();
    1262
    1263
            assert_eq!(ui.handle_command("xyz").unwrap(), UIAction::Continue);
    1264
            assert_eq!(ui.display_mode, initial_mode); // Should not change
    1265
        }
    1266
    1267
        // ========== Refresh and Update Tests ==========
    1268
    1269
        #[test]
    1270
        fn test_refresh_timing() {
    1271
            let ui = create_test_ui();
    1272
    1273
            // Check that refresh interval is properly set
    1274
            assert_eq!(ui.refresh_interval, Duration::from_millis(500));
    1275
    1276
            // Check that last_refresh is recent
    1277
            let now = Instant::now();
    1278
            assert!(now.duration_since(ui.last_refresh) < Duration::from_secs(1));
    1279
        }
    1280
    1281
        #[test]
    1282
        fn test_auto_refresh_config() {
    1283
            let mut config = create_test_ui_config();
    1284
            config.auto_refresh = true;
    1285
            config.refresh_interval_ms = 2000;
    1286
    1287
            let ui = create_test_ui_with_config(config);
    1288
            assert!(ui.config.auto_refresh);
    1289
            assert_eq!(ui.refresh_interval, Duration::from_millis(2000));
    1290
        }
    1291
    1292
        #[test]
    1293
        fn test_terminal_size_initialization() {
    1294
            let ui = create_test_ui();
    1295
            // Terminal size should be initialized to default values
    1296
            assert!(ui.terminal_size.0 > 0);
    1297
            assert!(ui.terminal_size.1 > 0);
    1298
        }
    1299
    1300
        // ========== Data Formatting Tests ==========
    1301
    1302
        #[test]
    1303
        fn test_format_bytes() {
    1304
            let ui = create_test_ui();
    1305
    1306
            assert_eq!(ui.format_bytes(512), "512B");
    1307
            assert_eq!(ui.format_bytes(1536), "1.50KB");  // 1.5 * 1024
    1308
            assert_eq!(ui.format_bytes(1024 * 1024), "1.00MB");
    1309
            assert_eq!(ui.format_bytes(1024 * 1024 * 1024), "1.00GB");
    1310
        }
    1311
    1312
        #[test]
    1313
        fn test_format_timestamp() {
    1314
            let ui = create_test_ui();
    1315
            let timestamp = std::time::SystemTime::now();
    1316
            let formatted = ui.format_timestamp(timestamp);
    1317
    1318
            // Should be a string representation of seconds since epoch
    1319
            assert!(!formatted.is_empty());
    1320
            // Should be numeric
    1321
            assert!(formatted.parse::<u64>().is_ok());
    1322
        }
    1323
    1324
        #[test]
    1325
        fn test_sample_data_creation() {
    1326
            let ui = create_test_ui();
    1327
            let stages = ui.get_sample_stages();
    1328
    1329
            assert_eq!(stages.len(), 3);
    1330
            assert_eq!(stages[0].stage_id, "load_data");
    1331
            assert_eq!(stages[1].stage_id, "filter_age");
    1332
            assert_eq!(stages[2].stage_id, "group_by_city");
    1333
        }
    1334
    1335
        #[test]
    1336
        fn test_sample_stage_detail() {
    1337
            let ui = create_test_ui();
    1338
            let stage = ui.get_sample_stage("any_id");
    1339
    1340
            assert_eq!(stage.stage_id, "load_data");
    1341
            assert_eq!(stage.stage_name, "Load CSV Data");
    1342
            assert!(stage.execution_time.is_some());
    1343
            assert!(stage.memory_usage.is_some());
    1344
            assert!(stage.rows_processed.is_some());
    1345
        }
    1346
    1347
        #[test]
    1348
        fn test_sample_materialized_data_creation() {
    1349
            let ui = create_test_ui();
    1350
            let frame = ui.get_sample_materialized_data("test_id");
    1351
    1352
            assert_eq!(frame.stage_id, "load_data");
    1353
            assert_eq!(frame.schema.columns.len(), 3);
    1354
            assert_eq!(frame.sample_data.len(), 3);
    1355
            assert_eq!(frame.total_rows, 10000);
    1356
        }
    1357
    1358
        // ========== Color Support Tests ==========
    1359
    1360
        #[test]
    1361
        fn test_colors_enabled() {
    1362
            let config_with_colors = UIConfig { enable_colors: true, ..create_test_ui_config() };
    1363
            let ui_with_colors = create_test_ui_with_config(config_with_colors);
    1364
            assert!(ui_with_colors.colors_enabled);
    1365
    1366
            let config_without_colors = UIConfig { enable_colors: false, ..create_test_ui_config() };
    1367
            let ui_without_colors = create_test_ui_with_config(config_without_colors);
    1368
            assert!(!ui_without_colors.colors_enabled);
    1369
        }
    1370
    1371
        #[test]
    1372
        fn test_color_configuration() {
    1373
            // Test that color setting from config is properly applied
    1374
            let mut ui = create_test_ui();
    1375
            assert!(!ui.colors_enabled); // Test config has colors disabled
    1376
    1377
            // Test that color setting can be changed
    1378
            ui.colors_enabled = true;
    1379
            assert!(ui.colors_enabled);
    1380
        }
    1381
    1382
        #[test]
    1383
        fn test_color_impact_on_rendering() {
    1384
            let mut ui = create_test_ui();
    1385
    1386
            // Test with colors disabled (should not crash)
    1387
            ui.colors_enabled = false;
    1388
            let result = ui.render_overview();
    1389
            assert!(result.is_ok());
    1390
    1391
            // Test with colors enabled (should not crash)
    1392
            ui.colors_enabled = true;
    1393
            let result = ui.render_overview();
    1394
            assert!(result.is_ok());
    1395
        }
    1396
    1397
        // ========== Configuration Tests ==========
    1398
    1399
        #[test]
    1400
        fn test_ui_configuration_settings() {
    1401
            let config = UIConfig {
    1402
                max_preview_rows: 10,
    1403
                max_history_events: 50,
    1404
                auto_refresh: false,
    1405
                refresh_interval_ms: 2000,
    1406
                show_metrics: false,
    1407
                enable_colors: true,
    1408
                compact_mode: true,
    1409
            };
    1410
    1411
            let ui = create_test_ui_with_config(config);
    1412
            assert_eq!(ui.config.max_preview_rows, 10);
    1413
            assert_eq!(ui.config.max_history_events, 50);
    1414
            assert!(!ui.config.auto_refresh);
    1415
            assert_eq!(ui.config.refresh_interval_ms, 2000);
    1416
            assert!(!ui.config.show_metrics);
    1417
            assert!(ui.config.enable_colors);
    1418
            assert!(ui.config.compact_mode);
    1419
        }
    1420
    1421
        #[test]
    1422
        fn test_display_mode_variations() {
    1423
            // Test that all display modes can be created
    1424
            let modes = vec![
    1425
                DisplayMode::Overview,
    1426
                DisplayMode::StageDetail("test".to_string()),
    1427
                DisplayMode::Breakpoints,
    1428
                DisplayMode::DataViewer("data_test".to_string()),
    1429
                DisplayMode::Metrics,
    1430
                DisplayMode::History,
    1431
                DisplayMode::Diff("a".to_string(), "b".to_string()),
    1432
                DisplayMode::Help,
    1433
            ];
    1434
    1435
            for mode in modes {
    1436
                // Each mode should be created without errors
    1437
                assert_ne!(format!("{mode:?}"), "");
    1438
            }
    1439
        }
    1440
    1441
        // ========== Integration Tests ==========
    1442
    1443
        #[test]
    1444
        fn test_refresh_display_all_modes() {
    1445
            let mut ui = create_test_ui();
    1446
    1447
            // Test different display modes
    1448
            ui.display_mode = DisplayMode::Overview;
    1449
            assert!(ui.refresh_display().is_ok());
    1450
    1451
            ui.display_mode = DisplayMode::Metrics;
    1452
            assert!(ui.refresh_display().is_ok());
    1453
    1454
            ui.display_mode = DisplayMode::Help;
    1455
            assert!(ui.refresh_display().is_ok());
    1456
    1457
            ui.display_mode = DisplayMode::History;
    1458
            assert!(ui.refresh_display().is_ok());
    1459
        }
    1460
    1461
        #[test]
    1462
        fn test_interactive_command_sequence() {
    1463
            let mut ui = create_test_ui();
    1464
    1465
            // Simulate user interaction sequence
    1466
            assert_eq!(ui.handle_command("metrics").unwrap(), UIAction::Continue);
    1467
            assert_eq!(ui.display_mode, DisplayMode::Metrics);
    1468
    1469
            assert_eq!(ui.handle_command("overview").unwrap(), UIAction::Continue);
    1470
            assert_eq!(ui.display_mode, DisplayMode::Overview);
    1471
    1472
            assert_eq!(ui.handle_command("breakpoints").unwrap(), UIAction::Continue);
    1473
            assert_eq!(ui.display_mode, DisplayMode::Breakpoints);
    1474
    1475
            assert_eq!(ui.handle_command("quit").unwrap(), UIAction::Exit);
    1476
        }
    1477
    1478
        #[test]
    1479
        fn test_config_variations() {
    1480
            let compact_config = UIConfig {
    1481
                max_preview_rows: 2,
    1482
                compact_mode: true,
    1483
                enable_colors: false,
    1484
                auto_refresh: false,
    1485
                ..Default::default()
    1486
            };
    1487
    1488
            let ui = create_test_ui_with_config(compact_config);
    1489
    1490
            // Should respect configuration settings
    1491
            assert_eq!(ui.config.max_preview_rows, 2);
    1492
            assert!(ui.config.compact_mode);
    1493
            assert!(!ui.colors_enabled);
    1494
            assert!(!ui.config.auto_refresh);
    1495
        }
    1496
    1497
        #[test]
    1498
        fn test_error_handling_graceful() {
    1499
            let ui = create_test_ui();
    1500
    1501
            // Test rendering with various stage IDs
    1502
            assert!(ui.render_stage_detail("nonexistent_stage").is_ok());
    1503
            assert!(ui.render_data_viewer("missing_data").is_ok());
    1504
            assert!(ui.render_diff("stage1", "stage2").is_ok());
    1505
    1506
            // All should handle gracefully without panicking
    1507
        }
    1508
    }