frentui 0.1.0

Interactive TUI for batch file renaming using freneng
Documentation
//! Tests for the App module

use frentui::app::App;
use frentui::section::{SectionId, SectionTrait};

#[test]
fn test_app_new() {
    let app = App::new();
    assert_eq!(app.current_section, SectionId::WorkingDirectory); // First focusable section
    assert!(!app.should_quit);
    assert_eq!(app.action_selection, Some(0));
    assert!(app.input_dialog.is_none());
    assert!(app.template_registry.is_none());
    assert_eq!(app.screen_scroll, 0);
    assert!(app.rename_history.is_none());
}

#[test]
fn test_app_default() {
    let app = App::default();
    assert_eq!(app.current_section, SectionId::WorkingDirectory); // First focusable section
}

#[test]
fn test_app_current_section_idx() {
    let app = App::new();
    let idx = app.current_section_idx();
    assert_eq!(app.sections[idx].id(), SectionId::WorkingDirectory); // First focusable section
}

#[test]
fn test_app_go_next_section() {
    let mut app = App::new();
    assert_eq!(app.current_section, SectionId::WorkingDirectory); // First focusable section
    
    app.go_next_section();
    // Should go to next focusable section (MatchFiles)
    assert_ne!(app.current_section, SectionId::WorkingDirectory);
    assert_eq!(app.action_selection, Some(0));
}

#[test]
fn test_app_go_previous_section() {
    let mut app = App::new();
    // Start at Header (which can't receive focus, so navigation will skip it)
    // Navigate to first focusable section
    app.go_next_section(); // Should skip Header and go to first focusable section
    let first_focusable = app.current_section;
    
    // Now go to next section
    app.go_next_section();
    let second_focusable = app.current_section;
    assert_ne!(first_focusable, second_focusable);
    
    // Go back
    app.go_previous_section();
    assert_eq!(app.current_section, first_focusable);
    assert_eq!(app.action_selection, Some(0));
}

#[test]
fn test_app_quit() {
    let mut app = App::new();
    assert!(!app.should_quit);
    app.quit();
    assert!(app.should_quit);
}

#[test]
fn test_app_get_template_registry() {
    let mut app = App::new();
    assert!(app.template_registry.is_none());
    
    {
        let registry = app.get_template_registry();
        assert!(registry.list_for_field(frentui::ui::dialog::TemplateField::RenamingRule).len() > 0);
    }
    
    // Second call should return same registry (lazy initialization)
    let registry2 = app.get_template_registry();
    let len = registry2.list_for_field(frentui::ui::dialog::TemplateField::RenamingRule).len();
    assert!(len > 0);
    
    // Verify template_registry is now Some
    assert!(app.template_registry.is_some());
}

#[test]
fn test_app_current_actions() {
    let app = App::new();
    let actions = app.current_actions();
    // WorkingDirectory section should have actions
    assert!(actions.len() > 0);
}

#[test]
fn test_app_sections_created() {
    let app = App::new();
    assert!(!app.sections.is_empty());
    
    // Check that all expected sections exist
    let section_ids: Vec<SectionId> = app.sections.iter().map(|s| s.id()).collect();
    assert!(section_ids.contains(&SectionId::Header));
    assert!(section_ids.contains(&SectionId::WorkingDirectory));
    assert!(section_ids.contains(&SectionId::MatchFiles));
    assert!(section_ids.contains(&SectionId::Exclusions));
    assert!(section_ids.contains(&SectionId::RenamingRule));
    assert!(section_ids.contains(&SectionId::PreviewPane));
    assert!(section_ids.contains(&SectionId::Validation));
    assert!(section_ids.contains(&SectionId::ApplyRenaming));
    assert!(section_ids.contains(&SectionId::Undo));
    assert!(section_ids.contains(&SectionId::Footer));
}

#[test]
fn test_app_section_can_focus() {
    let app = App::new();
    
    // Header and Footer should never be focusable
    assert!(!app.section_can_focus(SectionId::Header));
    assert!(!app.section_can_focus(SectionId::Footer));
    
    // WorkingDirectory should be focusable (has actions)
    assert!(app.section_can_focus(SectionId::WorkingDirectory));
    
    // MatchFiles should be focusable (has actions)
    assert!(app.section_can_focus(SectionId::MatchFiles));
}

#[test]
fn test_app_section_can_focus_with_content() {
    let mut app = App::new();
    
    // PreviewPane should be focusable if it has content
    // Initially, it should not be focusable (no files)
    assert!(!app.section_can_focus(SectionId::PreviewPane));
    
    // Add some files to make it focusable
    app.state.list.push(std::path::PathBuf::from("test1.txt"));
    app.state.new_names.push("test1_new.txt".to_string());
    
    // Now PreviewPane should be focusable
    assert!(app.section_can_focus(SectionId::PreviewPane));
}

#[test]
fn test_app_initialize() {
    let rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(async {
    let mut app = App::new();
    
    // Initialize should compute initial state
    let result = app.initialize().await;
    
    // Should succeed (or fail gracefully if there are issues)
    // We're mainly checking it doesn't panic
    assert!(result.is_ok() || result.is_err());
    });
}

#[test]
fn test_app_update_temp_preview_no_pattern() {
    let rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(async {
    let mut app = App::new();
    
    // No temp pattern set, should clear temp lists
    app.update_temp_preview().await;
    
    assert!(app.temp_list.is_empty());
    assert!(app.temp_new_names.is_empty());
    });
}

#[test]
fn test_app_update_temp_preview_invalid_pattern() {
    let rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(async {
    let mut app = App::new();
    
    // Set an invalid pattern
    app.temp_match_pattern = Some("[invalid[pattern".to_string());
    app.state.list.push(std::path::PathBuf::from("test.txt"));
    
    app.update_temp_preview().await;
    
    // Should show "invalid pattern" for new names
    assert!(!app.temp_new_names.is_empty());
    assert!(app.temp_new_names.iter().any(|n| n == "invalid pattern"));
    });
}

#[test]
fn test_app_update_temp_preview_exclusion_no_pattern() {
    let rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(async {
    let mut app = App::new();
    
    // No temp exclusion pattern set, should clear temp lists
    app.update_temp_preview_exclusion().await;
    
    assert!(app.temp_list.is_empty());
    assert!(app.temp_new_names.is_empty());
    });
}

#[test]
fn test_app_update_temp_preview_exclusion_invalid_pattern() {
    let rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(async {
    let mut app = App::new();
    
    // Set an invalid exclusion pattern
    app.temp_exclusion_pattern = Some("[invalid[pattern".to_string());
    app.state.raw_list.push(std::path::PathBuf::from("test.txt"));
    
    app.update_temp_preview_exclusion().await;
    
    // Should show "invalid pattern" for new names
    assert!(!app.temp_new_names.is_empty());
    assert!(app.temp_new_names.iter().any(|n| n == "invalid pattern"));
    });
}

#[test]
fn test_app_update_temp_preview_rename_no_pattern() {
    let rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(async {
    let mut app = App::new();
    
    // No temp rename pattern set, should clear temp lists
    app.update_temp_preview_rename().await;
    
    assert!(app.temp_list.is_empty());
    assert!(app.temp_new_names.is_empty());
    });
}

#[test]
fn test_app_update_temp_preview_rename_invalid_pattern() {
    let rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(async {
    let mut app = App::new();
    
    // Set an invalid rename pattern (freneng might accept this, so we need to check actual behavior)
    // Instead, let's test with a pattern that definitely causes an error
    app.temp_rename_pattern = Some("%INVALID%PATTERN%".to_string());
    app.state.list.push(std::path::PathBuf::from("test.txt"));
    app.state.new_names.push("test.txt".to_string()); // Need existing new_names
    
    app.update_temp_preview_rename().await;
    
    // The pattern might be valid in freneng, or might show "invalid pattern"
    // Just verify the function doesn't panic and produces some result
    // If the pattern is invalid, it should show "invalid pattern", otherwise it will compute new names
    // We need to ensure list has files for compute_new_names to work
    if !app.state.list.is_empty() {
        // If pattern is invalid, we get "invalid pattern", otherwise we get computed names
        assert!(!app.temp_new_names.is_empty() || app.temp_list.is_empty());
    }
    });
}