//! 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)); }