lala 0.1.0

A modern, lightweight text editor with GUI and CLI support for Markdown, HTML, Mermaid, and LaTeX
Documentation
/// Comprehensive tests for file operations
///
/// Tests cover:
/// - File reading and writing
/// - Directory traversal
/// - File tree operations
/// - UTF-8 encoding
/// - Japanese filename support
use lala::core_engine::{Buffer, BufferId};
use lala::file_tree::FileTree;
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;

// === File Reading Tests ===

#[test]
fn test_read_simple_file() {
    let temp_dir = TempDir::new().unwrap();
    let file_path = temp_dir.path().join("test.txt");

    fs::write(&file_path, "Hello, World!").unwrap();

    let content = fs::read_to_string(&file_path).unwrap();
    assert_eq!(content, "Hello, World!");
}

#[test]
fn test_read_multiline_file() {
    let temp_dir = TempDir::new().unwrap();
    let file_path = temp_dir.path().join("test.txt");

    let text = "Line 1\nLine 2\nLine 3\n";
    fs::write(&file_path, text).unwrap();

    let content = fs::read_to_string(&file_path).unwrap();
    assert_eq!(content, text);
}

#[test]
fn test_read_empty_file() {
    let temp_dir = TempDir::new().unwrap();
    let file_path = temp_dir.path().join("empty.txt");

    fs::write(&file_path, "").unwrap();

    let content = fs::read_to_string(&file_path).unwrap();
    assert_eq!(content, "");
}

#[test]
fn test_read_utf8_file() {
    let temp_dir = TempDir::new().unwrap();
    let file_path = temp_dir.path().join("utf8.txt");

    let text = "Hello 世界 😀 Привет";
    fs::write(&file_path, text).unwrap();

    let content = fs::read_to_string(&file_path).unwrap();
    assert_eq!(content, text);
}

#[test]
fn test_read_japanese_file() {
    let temp_dir = TempDir::new().unwrap();
    let file_path = temp_dir.path().join("japanese.txt");

    let text = "こんにちは、世界!\nこれはテストです。\n日本語のファイル。";
    fs::write(&file_path, text).unwrap();

    let content = fs::read_to_string(&file_path).unwrap();
    assert_eq!(content, text);
}

// === File Writing Tests ===

#[test]
fn test_write_simple_file() {
    let temp_dir = TempDir::new().unwrap();
    let file_path = temp_dir.path().join("output.txt");

    let text = "Test output";
    fs::write(&file_path, text).unwrap();

    let content = fs::read_to_string(&file_path).unwrap();
    assert_eq!(content, text);
}

#[test]
fn test_write_overwrite_file() {
    let temp_dir = TempDir::new().unwrap();
    let file_path = temp_dir.path().join("overwrite.txt");

    fs::write(&file_path, "Original").unwrap();
    fs::write(&file_path, "Overwritten").unwrap();

    let content = fs::read_to_string(&file_path).unwrap();
    assert_eq!(content, "Overwritten");
}

#[test]
fn test_write_japanese_file() {
    let temp_dir = TempDir::new().unwrap();
    let file_path = temp_dir.path().join("日本語.txt");

    let text = "日本語のファイル名とコンテンツ";
    fs::write(&file_path, text).unwrap();

    let content = fs::read_to_string(&file_path).unwrap();
    assert_eq!(content, text);
    assert!(file_path.exists());
}

#[test]
fn test_write_large_file() {
    let temp_dir = TempDir::new().unwrap();
    let file_path = temp_dir.path().join("large.txt");

    let text = "x".repeat(1_000_000); // 1MB
    fs::write(&file_path, &text).unwrap();

    let metadata = fs::metadata(&file_path).unwrap();
    assert_eq!(metadata.len(), 1_000_000);
}

// === Buffer Save/Load Tests ===

#[test]
fn test_buffer_save_and_load() {
    let temp_dir = TempDir::new().unwrap();
    let file_path = temp_dir.path().join("buffer.txt");

    // Create and save buffer
    let mut buffer = Buffer::from_string(
        BufferId(0),
        "Test content".to_string(),
        Some(file_path.clone()),
    );

    // Simulate save
    fs::write(&file_path, buffer.content()).unwrap();
    buffer.mark_clean();

    assert!(!buffer.is_dirty());

    // Load back
    let loaded_content = fs::read_to_string(&file_path).unwrap();
    let loaded_buffer = Buffer::from_string(BufferId(1), loaded_content, Some(file_path.clone()));

    assert_eq!(loaded_buffer.content(), buffer.content());
}

#[test]
fn test_buffer_modified_flag_after_save() {
    let temp_dir = TempDir::new().unwrap();
    let file_path = temp_dir.path().join("modified.txt");

    let mut buffer =
        Buffer::from_string(BufferId(0), "Original".to_string(), Some(file_path.clone()));

    // Make changes
    use lala::core_engine::{Position, Range};
    buffer
        .replace_range(
            Range::new(Position::new(0, 0), Position::new(0, 8)),
            "Modified",
        )
        .unwrap();

    assert!(buffer.is_dirty());

    // Save
    fs::write(&file_path, buffer.content()).unwrap();
    buffer.mark_clean();

    assert!(!buffer.is_dirty());
}

// === Directory Operations ===

#[test]
fn test_create_directory() {
    let temp_dir = TempDir::new().unwrap();
    let dir_path = temp_dir.path().join("subdir");

    fs::create_dir(&dir_path).unwrap();
    assert!(dir_path.exists());
    assert!(dir_path.is_dir());
}

#[test]
fn test_create_nested_directories() {
    let temp_dir = TempDir::new().unwrap();
    let dir_path = temp_dir.path().join("a").join("b").join("c");

    fs::create_dir_all(&dir_path).unwrap();
    assert!(dir_path.exists());
    assert!(dir_path.is_dir());
}

#[test]
fn test_list_directory_contents() {
    let temp_dir = TempDir::new().unwrap();

    // Create some files
    fs::write(temp_dir.path().join("file1.txt"), "content1").unwrap();
    fs::write(temp_dir.path().join("file2.txt"), "content2").unwrap();
    fs::create_dir(temp_dir.path().join("subdir")).unwrap();

    let entries: Vec<_> = fs::read_dir(temp_dir.path())
        .unwrap()
        .map(|e| e.unwrap().file_name().to_string_lossy().to_string())
        .collect();

    assert!(entries.contains(&"file1.txt".to_string()));
    assert!(entries.contains(&"file2.txt".to_string()));
    assert!(entries.contains(&"subdir".to_string()));
}

// === File Tree Tests ===

#[test]
fn test_file_tree_creation() {
    let _tree = FileTree::default();
    // FileTree doesn't have is_empty method, just verify it was created
}

#[test]
fn test_file_tree_from_directory() {
    let temp_dir = TempDir::new().unwrap();

    // Create test structure
    fs::write(temp_dir.path().join("file1.txt"), "content1").unwrap();
    fs::write(temp_dir.path().join("file2.txt"), "content2").unwrap();
    fs::create_dir(temp_dir.path().join("subdir")).unwrap();
    fs::write(temp_dir.path().join("subdir").join("file3.txt"), "content3").unwrap();

    let tree = FileTree::new(temp_dir.path().to_path_buf());
    // FileTree API doesn't have is_empty, just verify it was created
    let _ = tree;
}

#[test]
fn test_file_tree_with_japanese_names() {
    let temp_dir = TempDir::new().unwrap();

    // Create files with Japanese names
    fs::write(temp_dir.path().join("日本語.txt"), "内容").unwrap();
    fs::write(temp_dir.path().join("テスト.md"), "# タイトル").unwrap();

    let tree = FileTree::new(temp_dir.path().to_path_buf());
    // FileTree API doesn't have is_empty, just verify it was created
    let _ = tree;
}

// === Edge Cases ===

#[test]
fn test_read_nonexistent_file() {
    let result = fs::read_to_string("/nonexistent/file/path.txt");
    assert!(result.is_err());
}

#[test]
fn test_write_to_readonly_location() {
    // Try to write to root (should fail on most systems)
    let result = fs::write("/cannot_write_here.txt", "content");
    assert!(result.is_err());
}

#[test]
fn test_file_with_special_characters() {
    let temp_dir = TempDir::new().unwrap();

    // File with spaces and special chars (avoiding problematic ones)
    let filename = "file with spaces.txt";
    let file_path = temp_dir.path().join(filename);

    fs::write(&file_path, "content").unwrap();
    assert!(file_path.exists());

    let content = fs::read_to_string(&file_path).unwrap();
    assert_eq!(content, "content");
}

// === Line Ending Tests ===

#[test]
fn test_unix_line_endings() {
    let temp_dir = TempDir::new().unwrap();
    let file_path = temp_dir.path().join("unix.txt");

    let text = "Line 1\nLine 2\nLine 3\n";
    fs::write(&file_path, text).unwrap();

    let content = fs::read_to_string(&file_path).unwrap();
    assert_eq!(content, text);
}

#[test]
fn test_windows_line_endings() {
    let temp_dir = TempDir::new().unwrap();
    let file_path = temp_dir.path().join("windows.txt");

    let text = "Line 1\r\nLine 2\r\nLine 3\r\n";
    fs::write(&file_path, text).unwrap();

    let content = fs::read_to_string(&file_path).unwrap();
    assert_eq!(content, text);
}

#[test]
fn test_mixed_line_endings() {
    let temp_dir = TempDir::new().unwrap();
    let file_path = temp_dir.path().join("mixed.txt");

    let text = "Line 1\nLine 2\r\nLine 3\n";
    fs::write(&file_path, text).unwrap();

    let content = fs::read_to_string(&file_path).unwrap();
    assert_eq!(content, text);
}

// === Performance Tests ===

#[test]
fn test_many_small_files() {
    let temp_dir = TempDir::new().unwrap();

    // Create 100 small files
    for i in 0..100 {
        let file_path = temp_dir.path().join(format!("file_{i}.txt"));
        fs::write(&file_path, format!("Content {i}")).unwrap();
    }

    let entries: Vec<_> = fs::read_dir(temp_dir.path()).unwrap().collect();
    assert_eq!(entries.len(), 100);
}

#[test]
fn test_deep_directory_structure() {
    let temp_dir = TempDir::new().unwrap();

    // Create deep nested structure (10 levels)
    let mut path = PathBuf::from(temp_dir.path());
    for i in 0..10 {
        path = path.join(format!("level{i}"));
    }

    fs::create_dir_all(&path).unwrap();
    assert!(path.exists());

    // Write file at deepest level
    let file_path = path.join("deep.txt");
    fs::write(&file_path, "Deep content").unwrap();
    assert!(file_path.exists());
}