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
}