sal-text 0.1.0

SAL Text - Text processing and manipulation utilities with regex, templating, and normalization
Documentation
//! Unit tests for text replacement functionality
//!
//! These tests validate the TextReplacer including:
//! - Literal string replacement
//! - Regex pattern replacement
//! - Multiple chained replacements
//! - File operations (read, write, in-place)
//! - Error handling and edge cases

use sal_text::TextReplacer;
use std::fs;
use tempfile::NamedTempFile;

#[test]
fn test_text_replacer_literal_single() {
    let replacer = TextReplacer::builder()
        .pattern("hello")
        .replacement("hi")
        .regex(false)
        .build()
        .expect("Failed to build replacer");

    let result = replacer.replace("hello world, hello universe");
    assert_eq!(result, "hi world, hi universe");
}

#[test]
fn test_text_replacer_regex_single() {
    let replacer = TextReplacer::builder()
        .pattern(r"\d+")
        .replacement("NUMBER")
        .regex(true)
        .build()
        .expect("Failed to build replacer");

    let result = replacer.replace("There are 123 items and 456 more");
    assert_eq!(result, "There are NUMBER items and NUMBER more");
}

#[test]
fn test_text_replacer_multiple_operations() {
    let replacer = TextReplacer::builder()
        .pattern(r"\d+")
        .replacement("NUMBER")
        .regex(true)
        .and()
        .pattern("world")
        .replacement("universe")
        .regex(false)
        .build()
        .expect("Failed to build replacer");

    let result = replacer.replace("Hello world, there are 123 items");
    assert_eq!(result, "Hello universe, there are NUMBER items");
}

#[test]
fn test_text_replacer_chained_operations() {
    let replacer = TextReplacer::builder()
        .pattern("cat")
        .replacement("dog")
        .regex(false)
        .and()
        .pattern("dog")
        .replacement("animal")
        .regex(false)
        .build()
        .expect("Failed to build replacer");

    // Operations are applied in sequence, so "cat" -> "dog" -> "animal"
    let result = replacer.replace("The cat sat on the mat");
    assert_eq!(result, "The animal sat on the mat");
}

#[test]
fn test_text_replacer_regex_capture_groups() {
    let replacer = TextReplacer::builder()
        .pattern(r"(\d{4})-(\d{2})-(\d{2})")
        .replacement("$3/$2/$1")
        .regex(true)
        .build()
        .expect("Failed to build replacer");

    let result = replacer.replace("Date: 2023-12-01");
    assert_eq!(result, "Date: 01/12/2023");
}

#[test]
fn test_text_replacer_case_sensitive() {
    let replacer = TextReplacer::builder()
        .pattern("Hello")
        .replacement("Hi")
        .regex(false)
        .build()
        .expect("Failed to build replacer");

    let result = replacer.replace("Hello world, hello universe");
    assert_eq!(result, "Hi world, hello universe");
}

#[test]
fn test_text_replacer_regex_case_insensitive() {
    let replacer = TextReplacer::builder()
        .pattern(r"(?i)hello")
        .replacement("Hi")
        .regex(true)
        .build()
        .expect("Failed to build replacer");

    let result = replacer.replace("Hello world, HELLO universe");
    assert_eq!(result, "Hi world, Hi universe");
}

#[test]
fn test_text_replacer_empty_input() {
    let replacer = TextReplacer::builder()
        .pattern("test")
        .replacement("replacement")
        .regex(false)
        .build()
        .expect("Failed to build replacer");

    let result = replacer.replace("");
    assert_eq!(result, "");
}

#[test]
fn test_text_replacer_no_matches() {
    let replacer = TextReplacer::builder()
        .pattern("xyz")
        .replacement("abc")
        .regex(false)
        .build()
        .expect("Failed to build replacer");

    let input = "Hello world";
    let result = replacer.replace(input);
    assert_eq!(result, input);
}

#[test]
fn test_text_replacer_file_operations() {
    // Create a temporary file with test content
    let temp_file = NamedTempFile::new().expect("Failed to create temp file");
    let test_content = "Hello world, there are 123 items";
    fs::write(temp_file.path(), test_content).expect("Failed to write to temp file");

    let replacer = TextReplacer::builder()
        .pattern(r"\d+")
        .replacement("NUMBER")
        .regex(true)
        .and()
        .pattern("world")
        .replacement("universe")
        .regex(false)
        .build()
        .expect("Failed to build replacer");

    // Test replace_file
    let result = replacer
        .replace_file(temp_file.path())
        .expect("Failed to replace file content");
    assert_eq!(result, "Hello universe, there are NUMBER items");

    // Verify original file is unchanged
    let original_content =
        fs::read_to_string(temp_file.path()).expect("Failed to read original file");
    assert_eq!(original_content, test_content);
}

#[test]
fn test_text_replacer_file_in_place() {
    // Create a temporary file with test content
    let temp_file = NamedTempFile::new().expect("Failed to create temp file");
    let test_content = "Hello world, there are 123 items";
    fs::write(temp_file.path(), test_content).expect("Failed to write to temp file");

    let replacer = TextReplacer::builder()
        .pattern("world")
        .replacement("universe")
        .regex(false)
        .build()
        .expect("Failed to build replacer");

    // Test replace_file_in_place
    replacer
        .replace_file_in_place(temp_file.path())
        .expect("Failed to replace file in place");

    // Verify file content was changed
    let new_content = fs::read_to_string(temp_file.path()).expect("Failed to read modified file");
    assert_eq!(new_content, "Hello universe, there are 123 items");
}

#[test]
fn test_text_replacer_file_to_file() {
    // Create source file
    let source_file = NamedTempFile::new().expect("Failed to create source file");
    let test_content = "Hello world, there are 123 items";
    fs::write(source_file.path(), test_content).expect("Failed to write to source file");

    // Create destination file
    let dest_file = NamedTempFile::new().expect("Failed to create dest file");

    let replacer = TextReplacer::builder()
        .pattern(r"\d+")
        .replacement("NUMBER")
        .regex(true)
        .build()
        .expect("Failed to build replacer");

    // Test replace_file_to
    replacer
        .replace_file_to(source_file.path(), dest_file.path())
        .expect("Failed to replace file to destination");

    // Verify source file is unchanged
    let source_content =
        fs::read_to_string(source_file.path()).expect("Failed to read source file");
    assert_eq!(source_content, test_content);

    // Verify destination file has replaced content
    let dest_content = fs::read_to_string(dest_file.path()).expect("Failed to read dest file");
    assert_eq!(dest_content, "Hello world, there are NUMBER items");
}

#[test]
fn test_text_replacer_invalid_regex() {
    let result = TextReplacer::builder()
        .pattern("[invalid regex")
        .replacement("test")
        .regex(true)
        .build();

    assert!(result.is_err());
}

#[test]
fn test_text_replacer_builder_default_regex_false() {
    let replacer = TextReplacer::builder()
        .pattern(r"\d+")
        .replacement("NUMBER")
        .build()
        .expect("Failed to build replacer");

    // Should treat as literal since regex defaults to false
    let result = replacer.replace(r"Match \d+ pattern");
    assert_eq!(result, "Match NUMBER pattern");
}

#[test]
fn test_text_replacer_complex_regex() {
    let replacer = TextReplacer::builder()
        .pattern(r"(\w+)@(\w+\.\w+)")
        .replacement("EMAIL_ADDRESS")
        .regex(true)
        .build()
        .expect("Failed to build replacer");

    let result = replacer.replace("Contact john@example.com or jane@test.org");
    assert_eq!(result, "Contact EMAIL_ADDRESS or EMAIL_ADDRESS");
}

#[test]
fn test_text_replacer_multiline_text() {
    let replacer = TextReplacer::builder()
        .pattern(r"^\s*//.*$")
        .replacement("")
        .regex(true)
        .build()
        .expect("Failed to build replacer");

    let input =
        "function test() {\n    // This is a comment\n    return true;\n    // Another comment\n}";
    let result = replacer.replace(input);

    // Note: This test depends on how the regex engine handles multiline mode
    // The actual behavior might need adjustment based on regex flags
    assert!(result.contains("function test()"));
    assert!(result.contains("return true;"));
}

#[test]
fn test_text_replacer_unicode_text() {
    let replacer = TextReplacer::builder()
        .pattern("café")
        .replacement("coffee")
        .regex(false)
        .build()
        .expect("Failed to build replacer");

    let result = replacer.replace("I love café in the morning");
    assert_eq!(result, "I love coffee in the morning");
}

#[test]
fn test_text_replacer_large_text() {
    let large_text = "word ".repeat(10000);

    let replacer = TextReplacer::builder()
        .pattern("word")
        .replacement("term")
        .regex(false)
        .build()
        .expect("Failed to build replacer");

    let result = replacer.replace(&large_text);
    assert_eq!(result, "term ".repeat(10000));
}