- Add a new crate `sal-text` for text manipulation utilities. - Integrate `sal-text` into the main `sal` crate. - Remove the previous `text` module from `sal`. This improves organization and allows for independent development of the `sal-text` library.
This commit is contained in:
255
text/tests/rhai/run_all_tests.rhai
Normal file
255
text/tests/rhai/run_all_tests.rhai
Normal file
@@ -0,0 +1,255 @@
|
||||
// Text Rhai Test Runner
|
||||
//
|
||||
// This script runs all Text-related Rhai tests and reports results.
|
||||
|
||||
print("=== Text Rhai Test Suite ===");
|
||||
print("Running comprehensive tests for Text Rhai integration...\n");
|
||||
|
||||
let total_tests = 0;
|
||||
let passed_tests = 0;
|
||||
let failed_tests = 0;
|
||||
|
||||
// Test 1: Text Indentation Functions
|
||||
print("Test 1: Text Indentation Functions");
|
||||
total_tests += 1;
|
||||
try {
|
||||
let indented = " line 1\n line 2\n line 3";
|
||||
let dedented = dedent(indented);
|
||||
|
||||
let text = "line 1\nline 2";
|
||||
let prefixed = prefix(text, "> ");
|
||||
|
||||
if dedented == "line 1\nline 2\n line 3" && prefixed == "> line 1\n> line 2" {
|
||||
passed_tests += 1;
|
||||
print("✓ PASSED: Text indentation functions work correctly");
|
||||
} else {
|
||||
failed_tests += 1;
|
||||
print("✗ FAILED: Text indentation functions returned unexpected results");
|
||||
}
|
||||
} catch(err) {
|
||||
failed_tests += 1;
|
||||
print(`✗ ERROR: Text indentation test failed - ${err}`);
|
||||
}
|
||||
|
||||
// Test 2: String Normalization Functions
|
||||
print("\nTest 2: String Normalization Functions");
|
||||
total_tests += 1;
|
||||
try {
|
||||
let unsafe_name = "User's File [Draft].txt";
|
||||
let safe_name = name_fix(unsafe_name);
|
||||
|
||||
let unsafe_path = "/path/to/User's File.txt";
|
||||
let safe_path = path_fix(unsafe_path);
|
||||
|
||||
if safe_name == "user_s_file_draft_.txt" && safe_path == "/path/to/user_s_file.txt" {
|
||||
passed_tests += 1;
|
||||
print("✓ PASSED: String normalization functions work correctly");
|
||||
} else {
|
||||
failed_tests += 1;
|
||||
print(`✗ FAILED: String normalization - expected 'user_s_file_draft_.txt' and '/path/to/user_s_file.txt', got '${safe_name}' and '${safe_path}'`);
|
||||
}
|
||||
} catch(err) {
|
||||
failed_tests += 1;
|
||||
print(`✗ ERROR: String normalization test failed - ${err}`);
|
||||
}
|
||||
|
||||
// Test 3: TextReplacer Builder Pattern
|
||||
print("\nTest 3: TextReplacer Builder Pattern");
|
||||
total_tests += 1;
|
||||
try {
|
||||
let builder = text_replacer_new();
|
||||
builder = pattern(builder, "hello");
|
||||
builder = replacement(builder, "hi");
|
||||
builder = regex(builder, false);
|
||||
|
||||
let replacer = build(builder);
|
||||
let result = replace(replacer, "hello world, hello universe");
|
||||
|
||||
if result == "hi world, hi universe" {
|
||||
passed_tests += 1;
|
||||
print("✓ PASSED: TextReplacer builder pattern works correctly");
|
||||
} else {
|
||||
failed_tests += 1;
|
||||
print(`✗ FAILED: TextReplacer - expected 'hi world, hi universe', got '${result}'`);
|
||||
}
|
||||
} catch(err) {
|
||||
failed_tests += 1;
|
||||
print(`✗ ERROR: TextReplacer builder test failed - ${err}`);
|
||||
}
|
||||
|
||||
// Test 4: TextReplacer with Regex
|
||||
print("\nTest 4: TextReplacer with Regex");
|
||||
total_tests += 1;
|
||||
try {
|
||||
let builder = text_replacer_new();
|
||||
builder = pattern(builder, "\\d+");
|
||||
builder = replacement(builder, "NUMBER");
|
||||
builder = regex(builder, true);
|
||||
|
||||
let replacer = build(builder);
|
||||
let result = replace(replacer, "There are 123 items and 456 more");
|
||||
|
||||
if result == "There are NUMBER items and NUMBER more" {
|
||||
passed_tests += 1;
|
||||
print("✓ PASSED: TextReplacer regex functionality works correctly");
|
||||
} else {
|
||||
failed_tests += 1;
|
||||
print(`✗ FAILED: TextReplacer regex - expected 'There are NUMBER items and NUMBER more', got '${result}'`);
|
||||
}
|
||||
} catch(err) {
|
||||
failed_tests += 1;
|
||||
print(`✗ ERROR: TextReplacer regex test failed - ${err}`);
|
||||
}
|
||||
|
||||
// Test 5: TextReplacer Chained Operations
|
||||
print("\nTest 5: TextReplacer Chained Operations");
|
||||
total_tests += 1;
|
||||
try {
|
||||
let builder = text_replacer_new();
|
||||
builder = pattern(builder, "world");
|
||||
builder = replacement(builder, "universe");
|
||||
builder = regex(builder, false);
|
||||
builder = and(builder);
|
||||
builder = pattern(builder, "\\d+");
|
||||
builder = replacement(builder, "NUMBER");
|
||||
builder = regex(builder, true);
|
||||
|
||||
let replacer = build(builder);
|
||||
let result = replace(replacer, "Hello world, there are 123 items");
|
||||
|
||||
if result == "Hello universe, there are NUMBER items" {
|
||||
passed_tests += 1;
|
||||
print("✓ PASSED: TextReplacer chained operations work correctly");
|
||||
} else {
|
||||
failed_tests += 1;
|
||||
print(`✗ FAILED: TextReplacer chained - expected 'Hello universe, there are NUMBER items', got '${result}'`);
|
||||
}
|
||||
} catch(err) {
|
||||
failed_tests += 1;
|
||||
print(`✗ ERROR: TextReplacer chained operations test failed - ${err}`);
|
||||
}
|
||||
|
||||
// Test 6: Error Handling - Invalid Regex
|
||||
print("\nTest 6: Error Handling - Invalid Regex");
|
||||
total_tests += 1;
|
||||
try {
|
||||
let builder = text_replacer_new();
|
||||
builder = pattern(builder, "[invalid regex");
|
||||
builder = replacement(builder, "test");
|
||||
builder = regex(builder, true);
|
||||
let replacer = build(builder);
|
||||
|
||||
failed_tests += 1;
|
||||
print("✗ FAILED: Should have failed with invalid regex");
|
||||
} catch(err) {
|
||||
passed_tests += 1;
|
||||
print("✓ PASSED: Invalid regex properly rejected");
|
||||
}
|
||||
|
||||
// Test 7: Unicode Handling
|
||||
print("\nTest 7: Unicode Handling");
|
||||
total_tests += 1;
|
||||
try {
|
||||
let unicode_text = " Hello 世界\n Goodbye 世界";
|
||||
let dedented = dedent(unicode_text);
|
||||
|
||||
let unicode_name = "Café";
|
||||
let fixed_name = name_fix(unicode_name);
|
||||
|
||||
let unicode_prefix = prefix("Hello 世界", "🔹 ");
|
||||
|
||||
if dedented == "Hello 世界\nGoodbye 世界" &&
|
||||
fixed_name == "caf" &&
|
||||
unicode_prefix == "🔹 Hello 世界" {
|
||||
passed_tests += 1;
|
||||
print("✓ PASSED: Unicode handling works correctly");
|
||||
} else {
|
||||
failed_tests += 1;
|
||||
print("✗ FAILED: Unicode handling returned unexpected results");
|
||||
}
|
||||
} catch(err) {
|
||||
failed_tests += 1;
|
||||
print(`✗ ERROR: Unicode handling test failed - ${err}`);
|
||||
}
|
||||
|
||||
// Test 8: Edge Cases
|
||||
print("\nTest 8: Edge Cases");
|
||||
total_tests += 1;
|
||||
try {
|
||||
let empty_dedent = dedent("");
|
||||
let empty_prefix = prefix("test", "");
|
||||
let empty_name_fix = name_fix("");
|
||||
|
||||
if empty_dedent == "" && empty_prefix == "test" && empty_name_fix == "" {
|
||||
passed_tests += 1;
|
||||
print("✓ PASSED: Edge cases handled correctly");
|
||||
} else {
|
||||
failed_tests += 1;
|
||||
print("✗ FAILED: Edge cases returned unexpected results");
|
||||
}
|
||||
} catch(err) {
|
||||
failed_tests += 1;
|
||||
print(`✗ ERROR: Edge cases test failed - ${err}`);
|
||||
}
|
||||
|
||||
// Test 9: Complex Workflow
|
||||
print("\nTest 9: Complex Text Processing Workflow");
|
||||
total_tests += 1;
|
||||
try {
|
||||
// Normalize filename
|
||||
let unsafe_filename = "User's Script [Draft].py";
|
||||
let safe_filename = name_fix(unsafe_filename);
|
||||
|
||||
// Process code
|
||||
let indented_code = " def hello():\n print('Hello World')\n return True";
|
||||
let dedented_code = dedent(indented_code);
|
||||
let commented_code = prefix(dedented_code, "# ");
|
||||
|
||||
// Replace text
|
||||
let builder = text_replacer_new();
|
||||
builder = pattern(builder, "Hello World");
|
||||
builder = replacement(builder, "SAL Text");
|
||||
builder = regex(builder, false);
|
||||
|
||||
let replacer = build(builder);
|
||||
let final_code = replace(replacer, commented_code);
|
||||
|
||||
if safe_filename == "user_s_script_draft_.py" &&
|
||||
final_code.contains("# def hello():") &&
|
||||
final_code.contains("SAL Text") {
|
||||
passed_tests += 1;
|
||||
print("✓ PASSED: Complex workflow completed successfully");
|
||||
} else {
|
||||
failed_tests += 1;
|
||||
print("✗ FAILED: Complex workflow returned unexpected results");
|
||||
}
|
||||
} catch(err) {
|
||||
failed_tests += 1;
|
||||
print(`✗ ERROR: Complex workflow test failed - ${err}`);
|
||||
}
|
||||
|
||||
// Test 10: Template Builder Error Handling
|
||||
print("\nTest 10: Template Builder Error Handling");
|
||||
total_tests += 1;
|
||||
try {
|
||||
let builder = template_builder_open("/nonexistent/file.txt");
|
||||
failed_tests += 1;
|
||||
print("✗ FAILED: Should have failed with nonexistent file");
|
||||
} catch(err) {
|
||||
passed_tests += 1;
|
||||
print("✓ PASSED: Template builder properly handles nonexistent files");
|
||||
}
|
||||
|
||||
// Print final results
|
||||
print("\n=== Test Results ===");
|
||||
print(`Total Tests: ${total_tests}`);
|
||||
print(`Passed: ${passed_tests}`);
|
||||
print(`Failed: ${failed_tests}`);
|
||||
|
||||
if failed_tests == 0 {
|
||||
print("\n✓ All tests passed!");
|
||||
} else {
|
||||
print(`\n✗ ${failed_tests} test(s) failed.`);
|
||||
}
|
||||
|
||||
print("\n=== Text Rhai Test Suite Completed ===");
|
351
text/tests/rhai_integration_tests.rs
Normal file
351
text/tests/rhai_integration_tests.rs
Normal file
@@ -0,0 +1,351 @@
|
||||
//! Rhai integration tests for Text module
|
||||
//!
|
||||
//! These tests validate the Rhai wrapper functions and ensure proper
|
||||
//! integration between Rust and Rhai for text processing operations.
|
||||
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use sal_text::rhai::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod rhai_integration_tests {
|
||||
use super::*;
|
||||
|
||||
fn create_test_engine() -> Engine {
|
||||
let mut engine = Engine::new();
|
||||
register_text_module(&mut engine).expect("Failed to register text module");
|
||||
engine
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rhai_module_registration() {
|
||||
let engine = create_test_engine();
|
||||
|
||||
// Test that the functions are registered by checking if they exist
|
||||
let script = r#"
|
||||
// Test that all text functions are available
|
||||
let functions_exist = true;
|
||||
functions_exist
|
||||
"#;
|
||||
|
||||
let result: Result<bool, Box<EvalAltResult>> = engine.eval(script);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dedent_function_exists() {
|
||||
let engine = create_test_engine();
|
||||
|
||||
let script = r#"
|
||||
let indented = " line 1\n line 2\n line 3";
|
||||
let result = dedent(indented);
|
||||
return result == "line 1\nline 2\n line 3";
|
||||
"#;
|
||||
|
||||
let result: Result<bool, Box<EvalAltResult>> = engine.eval(script);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prefix_function_exists() {
|
||||
let engine = create_test_engine();
|
||||
|
||||
let script = r#"
|
||||
let text = "line 1\nline 2";
|
||||
let result = prefix(text, "> ");
|
||||
return result == "> line 1\n> line 2";
|
||||
"#;
|
||||
|
||||
let result: Result<bool, Box<EvalAltResult>> = engine.eval(script);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_name_fix_function_exists() {
|
||||
let engine = create_test_engine();
|
||||
|
||||
let script = r#"
|
||||
let unsafe_name = "User's File [Draft].txt";
|
||||
let result = name_fix(unsafe_name);
|
||||
return result == "users_file_draft_.txt";
|
||||
"#;
|
||||
|
||||
let result: Result<bool, Box<EvalAltResult>> = engine.eval(script);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_fix_function_exists() {
|
||||
let engine = create_test_engine();
|
||||
|
||||
let script = r#"
|
||||
let unsafe_path = "/path/to/User's File.txt";
|
||||
let result = path_fix(unsafe_path);
|
||||
return result == "/path/to/users_file.txt";
|
||||
"#;
|
||||
|
||||
let result: Result<bool, Box<EvalAltResult>> = engine.eval(script);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_replacer_builder_creation() {
|
||||
let engine = create_test_engine();
|
||||
|
||||
let script = r#"
|
||||
let builder = text_replacer_builder();
|
||||
return type_of(builder) == "sal_text::replace::TextReplacerBuilder";
|
||||
"#;
|
||||
|
||||
let result: Result<bool, Box<EvalAltResult>> = engine.eval(script);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_replacer_workflow() {
|
||||
let engine = create_test_engine();
|
||||
|
||||
let script = r#"
|
||||
let builder = text_replacer_builder();
|
||||
builder = pattern(builder, "hello");
|
||||
builder = replacement(builder, "hi");
|
||||
builder = regex(builder, false);
|
||||
|
||||
let replacer = build(builder);
|
||||
let result = replace(replacer, "hello world, hello universe");
|
||||
|
||||
return result == "hi world, hi universe";
|
||||
"#;
|
||||
|
||||
let result: Result<bool, Box<EvalAltResult>> = engine.eval(script);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_replacer_regex_workflow() {
|
||||
let engine = create_test_engine();
|
||||
|
||||
let script = r#"
|
||||
let builder = text_replacer_builder();
|
||||
builder = pattern(builder, r"\d+");
|
||||
builder = replacement(builder, "NUMBER");
|
||||
builder = regex(builder, true);
|
||||
|
||||
let replacer = build(builder);
|
||||
let result = replace(replacer, "There are 123 items");
|
||||
|
||||
return result == "There are NUMBER items";
|
||||
"#;
|
||||
|
||||
let result: Result<bool, Box<EvalAltResult>> = engine.eval(script);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_replacer_chained_operations() {
|
||||
let engine = create_test_engine();
|
||||
|
||||
let script = r#"
|
||||
let builder = text_replacer_builder();
|
||||
builder = pattern(builder, "world");
|
||||
builder = replacement(builder, "universe");
|
||||
builder = regex(builder, false);
|
||||
builder = and(builder);
|
||||
builder = pattern(builder, r"\d+");
|
||||
builder = replacement(builder, "NUMBER");
|
||||
builder = regex(builder, true);
|
||||
|
||||
let replacer = build(builder);
|
||||
let result = replace(replacer, "Hello world, there are 123 items");
|
||||
|
||||
return result == "Hello universe, there are NUMBER items";
|
||||
"#;
|
||||
|
||||
let result: Result<bool, Box<EvalAltResult>> = engine.eval(script);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_builder_creation() {
|
||||
let engine = create_test_engine();
|
||||
|
||||
let script = r#"
|
||||
// We can't test file operations easily in unit tests,
|
||||
// but we can test that the function exists and returns the right type
|
||||
try {
|
||||
let builder = template_builder_open("/nonexistent/file.txt");
|
||||
return false; // Should have failed
|
||||
} catch(err) {
|
||||
return err.to_string().contains("error"); // Expected to fail
|
||||
}
|
||||
"#;
|
||||
|
||||
let result: Result<bool, Box<EvalAltResult>> = engine.eval(script);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_handling_invalid_regex() {
|
||||
let engine = create_test_engine();
|
||||
|
||||
let script = r#"
|
||||
try {
|
||||
let builder = text_replacer_builder();
|
||||
builder = pattern(builder, "[invalid regex");
|
||||
builder = replacement(builder, "test");
|
||||
builder = regex(builder, true);
|
||||
let replacer = build(builder);
|
||||
return false; // Should have failed
|
||||
} catch(err) {
|
||||
return true; // Expected to fail
|
||||
}
|
||||
"#;
|
||||
|
||||
let result: Result<bool, Box<EvalAltResult>> = engine.eval(script);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parameter_validation() {
|
||||
let engine = create_test_engine();
|
||||
|
||||
// Test that functions handle parameter validation correctly
|
||||
let script = r#"
|
||||
let test_results = [];
|
||||
|
||||
// Test empty string handling
|
||||
try {
|
||||
let result = dedent("");
|
||||
test_results.push(result == "");
|
||||
} catch(err) {
|
||||
test_results.push(false);
|
||||
}
|
||||
|
||||
// Test empty prefix
|
||||
try {
|
||||
let result = prefix("test", "");
|
||||
test_results.push(result == "test");
|
||||
} catch(err) {
|
||||
test_results.push(false);
|
||||
}
|
||||
|
||||
// Test empty name_fix
|
||||
try {
|
||||
let result = name_fix("");
|
||||
test_results.push(result == "");
|
||||
} catch(err) {
|
||||
test_results.push(false);
|
||||
}
|
||||
|
||||
return test_results;
|
||||
"#;
|
||||
|
||||
let result: Result<rhai::Array, Box<EvalAltResult>> = engine.eval(script);
|
||||
assert!(result.is_ok());
|
||||
let results = result.unwrap();
|
||||
|
||||
// All parameter validation tests should pass
|
||||
for (i, result) in results.iter().enumerate() {
|
||||
assert_eq!(
|
||||
result.as_bool().unwrap_or(false),
|
||||
true,
|
||||
"Parameter validation test {} failed",
|
||||
i
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unicode_handling() {
|
||||
let engine = create_test_engine();
|
||||
|
||||
let script = r#"
|
||||
let unicode_tests = [];
|
||||
|
||||
// Test dedent with unicode
|
||||
try {
|
||||
let text = " Hello 世界\n Goodbye 世界";
|
||||
let result = dedent(text);
|
||||
unicode_tests.push(result == "Hello 世界\nGoodbye 世界");
|
||||
} catch(err) {
|
||||
unicode_tests.push(false);
|
||||
}
|
||||
|
||||
// Test name_fix with unicode (should remove non-ASCII)
|
||||
try {
|
||||
let result = name_fix("Café");
|
||||
unicode_tests.push(result == "caf");
|
||||
} catch(err) {
|
||||
unicode_tests.push(false);
|
||||
}
|
||||
|
||||
// Test prefix with unicode
|
||||
try {
|
||||
let result = prefix("Hello 世界", "🔹 ");
|
||||
unicode_tests.push(result == "🔹 Hello 世界");
|
||||
} catch(err) {
|
||||
unicode_tests.push(false);
|
||||
}
|
||||
|
||||
return unicode_tests;
|
||||
"#;
|
||||
|
||||
let result: Result<rhai::Array, Box<EvalAltResult>> = engine.eval(script);
|
||||
assert!(result.is_ok());
|
||||
let results = result.unwrap();
|
||||
|
||||
// All unicode tests should pass
|
||||
for (i, result) in results.iter().enumerate() {
|
||||
assert_eq!(
|
||||
result.as_bool().unwrap_or(false),
|
||||
true,
|
||||
"Unicode test {} failed",
|
||||
i
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_text_processing_workflow() {
|
||||
let engine = create_test_engine();
|
||||
|
||||
let script = r#"
|
||||
// Simple workflow test
|
||||
let unsafe_filename = "User's Script [Draft].py";
|
||||
let safe_filename = name_fix(unsafe_filename);
|
||||
|
||||
let indented_code = " def hello():\n return True";
|
||||
let dedented_code = dedent(indented_code);
|
||||
|
||||
let results = [];
|
||||
results.push(safe_filename == "users_script_draft_.py");
|
||||
results.push(dedented_code.contains("def hello():"));
|
||||
|
||||
return results;
|
||||
"#;
|
||||
|
||||
let result: Result<rhai::Array, Box<EvalAltResult>> = engine.eval(script);
|
||||
assert!(result.is_ok());
|
||||
let results = result.unwrap();
|
||||
|
||||
// All workflow tests should pass
|
||||
for (i, result) in results.iter().enumerate() {
|
||||
assert_eq!(
|
||||
result.as_bool().unwrap_or(false),
|
||||
true,
|
||||
"Workflow test {} failed",
|
||||
i
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
174
text/tests/string_normalization_tests.rs
Normal file
174
text/tests/string_normalization_tests.rs
Normal file
@@ -0,0 +1,174 @@
|
||||
//! Unit tests for string normalization functionality
|
||||
//!
|
||||
//! These tests validate the name_fix and path_fix functions including:
|
||||
//! - Filename sanitization for safe filesystem usage
|
||||
//! - Path normalization preserving directory structure
|
||||
//! - Special character handling and replacement
|
||||
//! - Unicode character removal and ASCII conversion
|
||||
|
||||
use sal_text::{name_fix, path_fix};
|
||||
|
||||
#[test]
|
||||
fn test_name_fix_basic() {
|
||||
assert_eq!(name_fix("Hello World"), "hello_world");
|
||||
assert_eq!(name_fix("File-Name.txt"), "file_name.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_name_fix_special_characters() {
|
||||
assert_eq!(name_fix("Test!@#$%^&*()"), "test_");
|
||||
assert_eq!(name_fix("Space, Tab\t, Comma,"), "space_tab_comma_");
|
||||
assert_eq!(name_fix("Quotes\"'"), "quotes_");
|
||||
assert_eq!(name_fix("Brackets[]<>"), "brackets_");
|
||||
assert_eq!(name_fix("Operators=+-"), "operators_");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_name_fix_unicode_removal() {
|
||||
assert_eq!(name_fix("Café"), "caf");
|
||||
assert_eq!(name_fix("Résumé"), "rsum");
|
||||
assert_eq!(name_fix("Über"), "ber");
|
||||
assert_eq!(name_fix("Naïve"), "nave");
|
||||
assert_eq!(name_fix("Piñata"), "piata");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_name_fix_case_conversion() {
|
||||
assert_eq!(name_fix("UPPERCASE"), "uppercase");
|
||||
assert_eq!(name_fix("MixedCase"), "mixedcase");
|
||||
assert_eq!(name_fix("camelCase"), "camelcase");
|
||||
assert_eq!(name_fix("PascalCase"), "pascalcase");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_name_fix_consecutive_underscores() {
|
||||
assert_eq!(name_fix("Multiple Spaces"), "multiple_spaces");
|
||||
assert_eq!(name_fix("Special!!!Characters"), "special_characters");
|
||||
assert_eq!(name_fix("Mixed-_-Separators"), "mixed_separators");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_name_fix_file_extensions() {
|
||||
assert_eq!(name_fix("Document.PDF"), "document.pdf");
|
||||
assert_eq!(name_fix("Image.JPEG"), "image.jpeg");
|
||||
assert_eq!(name_fix("Archive.tar.gz"), "archive.tar.gz");
|
||||
assert_eq!(name_fix("Config.json"), "config.json");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_name_fix_empty_and_edge_cases() {
|
||||
assert_eq!(name_fix(""), "");
|
||||
assert_eq!(name_fix(" "), "_");
|
||||
assert_eq!(name_fix("!!!"), "_");
|
||||
assert_eq!(name_fix("___"), "_");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_name_fix_real_world_examples() {
|
||||
assert_eq!(name_fix("User's Report [Draft 1].md"), "users_report_draft_1_.md");
|
||||
assert_eq!(name_fix("Meeting Notes (2023-12-01).txt"), "meeting_notes_2023_12_01_.txt");
|
||||
assert_eq!(name_fix("Photo #123 - Vacation!.jpg"), "photo_123_vacation_.jpg");
|
||||
assert_eq!(name_fix("Project Plan v2.0 FINAL.docx"), "project_plan_v2.0_final.docx");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_fix_directory_paths() {
|
||||
assert_eq!(path_fix("/path/to/directory/"), "/path/to/directory/");
|
||||
assert_eq!(path_fix("./relative/path/"), "./relative/path/");
|
||||
assert_eq!(path_fix("../parent/path/"), "../parent/path/");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_fix_single_filename() {
|
||||
assert_eq!(path_fix("filename.txt"), "filename.txt");
|
||||
assert_eq!(path_fix("UPPER-file.md"), "upper_file.md");
|
||||
assert_eq!(path_fix("Special!File.pdf"), "special_file.pdf");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_fix_absolute_paths() {
|
||||
assert_eq!(path_fix("/path/to/File Name.txt"), "/path/to/file_name.txt");
|
||||
assert_eq!(path_fix("/absolute/path/to/DOCUMENT-123.pdf"), "/absolute/path/to/document_123.pdf");
|
||||
assert_eq!(path_fix("/home/user/Résumé.doc"), "/home/user/rsum.doc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_fix_relative_paths() {
|
||||
assert_eq!(path_fix("./relative/path/to/Document.PDF"), "./relative/path/to/document.pdf");
|
||||
assert_eq!(path_fix("../parent/Special File.txt"), "../parent/special_file.txt");
|
||||
assert_eq!(path_fix("subfolder/User's File.md"), "subfolder/users_file.md");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_fix_special_characters_in_filename() {
|
||||
assert_eq!(path_fix("/path/with/[special]<chars>.txt"), "/path/with/_special_chars_.txt");
|
||||
assert_eq!(path_fix("./folder/File!@#.pdf"), "./folder/file_.pdf");
|
||||
assert_eq!(path_fix("/data/Report (Final).docx"), "/data/report_final_.docx");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_fix_preserves_path_structure() {
|
||||
assert_eq!(path_fix("/very/long/path/to/some/Deep File.txt"), "/very/long/path/to/some/deep_file.txt");
|
||||
assert_eq!(path_fix("./a/b/c/d/e/Final Document.pdf"), "./a/b/c/d/e/final_document.pdf");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_fix_windows_style_paths() {
|
||||
// Note: These tests assume Unix-style path handling
|
||||
// In a real implementation, you might want to handle Windows paths differently
|
||||
assert_eq!(path_fix("C:\\Users\\Name\\Document.txt"), "c_users_name_document.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_fix_edge_cases() {
|
||||
assert_eq!(path_fix(""), "");
|
||||
assert_eq!(path_fix("/"), "/");
|
||||
assert_eq!(path_fix("./"), "./");
|
||||
assert_eq!(path_fix("../"), "../");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_fix_unicode_in_filename() {
|
||||
assert_eq!(path_fix("/path/to/Café.txt"), "/path/to/caf.txt");
|
||||
assert_eq!(path_fix("./folder/Naïve Document.pdf"), "./folder/nave_document.pdf");
|
||||
assert_eq!(path_fix("/home/user/Piñata Party.jpg"), "/home/user/piata_party.jpg");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_fix_complex_real_world_examples() {
|
||||
assert_eq!(
|
||||
path_fix("/Users/john/Documents/Project Files/Final Report (v2.1) [APPROVED].docx"),
|
||||
"/Users/john/Documents/Project Files/final_report_v2.1_approved_.docx"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
path_fix("./assets/images/Photo #123 - Vacation! (2023).jpg"),
|
||||
"./assets/images/photo_123_vacation_2023_.jpg"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
path_fix("/var/log/Application Logs/Error Log [2023-12-01].txt"),
|
||||
"/var/log/Application Logs/error_log_2023_12_01_.txt"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_name_fix_and_path_fix_consistency() {
|
||||
let filename = "User's Report [Draft].txt";
|
||||
let path = "/path/to/User's Report [Draft].txt";
|
||||
|
||||
let fixed_name = name_fix(filename);
|
||||
let fixed_path = path_fix(path);
|
||||
|
||||
// The filename part should be the same in both cases
|
||||
assert!(fixed_path.ends_with(&fixed_name));
|
||||
assert_eq!(fixed_name, "users_report_draft_.txt");
|
||||
assert_eq!(fixed_path, "/path/to/users_report_draft_.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalization_preserves_dots_in_extensions() {
|
||||
assert_eq!(name_fix("file.tar.gz"), "file.tar.gz");
|
||||
assert_eq!(name_fix("backup.2023.12.01.sql"), "backup.2023.12.01.sql");
|
||||
assert_eq!(path_fix("/path/to/archive.tar.bz2"), "/path/to/archive.tar.bz2");
|
||||
}
|
297
text/tests/template_tests.rs
Normal file
297
text/tests/template_tests.rs
Normal file
@@ -0,0 +1,297 @@
|
||||
//! Unit tests for template functionality
|
||||
//!
|
||||
//! These tests validate the TemplateBuilder including:
|
||||
//! - Template loading from files
|
||||
//! - Variable substitution (string, int, float, bool, array)
|
||||
//! - Template rendering to string and file
|
||||
//! - Error handling for missing variables and invalid templates
|
||||
//! - Complex template scenarios with loops and conditionals
|
||||
|
||||
use sal_text::TemplateBuilder;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
#[test]
|
||||
fn test_template_builder_basic_string_variable() {
|
||||
// Create a temporary template file
|
||||
let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
|
||||
let template_content = "Hello {{name}}!";
|
||||
fs::write(temp_file.path(), template_content).expect("Failed to write template");
|
||||
|
||||
let result = TemplateBuilder::open(temp_file.path())
|
||||
.expect("Failed to open template")
|
||||
.add_var("name", "World")
|
||||
.render()
|
||||
.expect("Failed to render template");
|
||||
|
||||
assert_eq!(result, "Hello World!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_builder_multiple_variables() {
|
||||
let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
|
||||
let template_content = "{{greeting}} {{name}}, you have {{count}} messages.";
|
||||
fs::write(temp_file.path(), template_content).expect("Failed to write template");
|
||||
|
||||
let result = TemplateBuilder::open(temp_file.path())
|
||||
.expect("Failed to open template")
|
||||
.add_var("greeting", "Hello")
|
||||
.add_var("name", "Alice")
|
||||
.add_var("count", 5)
|
||||
.render()
|
||||
.expect("Failed to render template");
|
||||
|
||||
assert_eq!(result, "Hello Alice, you have 5 messages.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_builder_different_types() {
|
||||
let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
|
||||
let template_content = "String: {{text}}, Int: {{number}}, Float: {{decimal}}, Bool: {{flag}}";
|
||||
fs::write(temp_file.path(), template_content).expect("Failed to write template");
|
||||
|
||||
let result = TemplateBuilder::open(temp_file.path())
|
||||
.expect("Failed to open template")
|
||||
.add_var("text", "hello")
|
||||
.add_var("number", 42)
|
||||
.add_var("decimal", 3.14)
|
||||
.add_var("flag", true)
|
||||
.render()
|
||||
.expect("Failed to render template");
|
||||
|
||||
assert_eq!(result, "String: hello, Int: 42, Float: 3.14, Bool: true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_builder_array_variable() {
|
||||
let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
|
||||
let template_content = "Items: {% for item in items %}{{item}}{% if not loop.last %}, {% endif %}{% endfor %}";
|
||||
fs::write(temp_file.path(), template_content).expect("Failed to write template");
|
||||
|
||||
let items = vec!["apple", "banana", "cherry"];
|
||||
let result = TemplateBuilder::open(temp_file.path())
|
||||
.expect("Failed to open template")
|
||||
.add_var("items", items)
|
||||
.render()
|
||||
.expect("Failed to render template");
|
||||
|
||||
assert_eq!(result, "Items: apple, banana, cherry");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_builder_add_vars_hashmap() {
|
||||
let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
|
||||
let template_content = "{{title}}: {{description}}";
|
||||
fs::write(temp_file.path(), template_content).expect("Failed to write template");
|
||||
|
||||
let mut vars = HashMap::new();
|
||||
vars.insert("title".to_string(), "Report".to_string());
|
||||
vars.insert("description".to_string(), "Monthly summary".to_string());
|
||||
|
||||
let result = TemplateBuilder::open(temp_file.path())
|
||||
.expect("Failed to open template")
|
||||
.add_vars(vars)
|
||||
.render()
|
||||
.expect("Failed to render template");
|
||||
|
||||
assert_eq!(result, "Report: Monthly summary");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_builder_render_to_file() {
|
||||
// Create template file
|
||||
let mut template_file = NamedTempFile::new().expect("Failed to create template file");
|
||||
let template_content = "Hello {{name}}, today is {{day}}.";
|
||||
fs::write(template_file.path(), template_content).expect("Failed to write template");
|
||||
|
||||
// Create output file
|
||||
let output_file = NamedTempFile::new().expect("Failed to create output file");
|
||||
|
||||
TemplateBuilder::open(template_file.path())
|
||||
.expect("Failed to open template")
|
||||
.add_var("name", "Bob")
|
||||
.add_var("day", "Monday")
|
||||
.render_to_file(output_file.path())
|
||||
.expect("Failed to render to file");
|
||||
|
||||
let result = fs::read_to_string(output_file.path()).expect("Failed to read output file");
|
||||
assert_eq!(result, "Hello Bob, today is Monday.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_builder_conditional() {
|
||||
let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
|
||||
let template_content = "{% if show_message %}Message: {{message}}{% else %}No message{% endif %}";
|
||||
fs::write(temp_file.path(), template_content).expect("Failed to write template");
|
||||
|
||||
// Test with condition true
|
||||
let result_true = TemplateBuilder::open(temp_file.path())
|
||||
.expect("Failed to open template")
|
||||
.add_var("show_message", true)
|
||||
.add_var("message", "Hello World")
|
||||
.render()
|
||||
.expect("Failed to render template");
|
||||
|
||||
assert_eq!(result_true, "Message: Hello World");
|
||||
|
||||
// Test with condition false
|
||||
let result_false = TemplateBuilder::open(temp_file.path())
|
||||
.expect("Failed to open template")
|
||||
.add_var("show_message", false)
|
||||
.add_var("message", "Hello World")
|
||||
.render()
|
||||
.expect("Failed to render template");
|
||||
|
||||
assert_eq!(result_false, "No message");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_builder_loop_with_index() {
|
||||
let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
|
||||
let template_content = "{% for item in items %}{{loop.index}}: {{item}}\n{% endfor %}";
|
||||
fs::write(temp_file.path(), template_content).expect("Failed to write template");
|
||||
|
||||
let items = vec!["first", "second", "third"];
|
||||
let result = TemplateBuilder::open(temp_file.path())
|
||||
.expect("Failed to open template")
|
||||
.add_var("items", items)
|
||||
.render()
|
||||
.expect("Failed to render template");
|
||||
|
||||
assert_eq!(result, "1: first\n2: second\n3: third\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_builder_nested_variables() {
|
||||
let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
|
||||
let template_content = "User: {{user.name}} ({{user.email}})";
|
||||
fs::write(temp_file.path(), template_content).expect("Failed to write template");
|
||||
|
||||
let mut user = HashMap::new();
|
||||
user.insert("name".to_string(), "John Doe".to_string());
|
||||
user.insert("email".to_string(), "john@example.com".to_string());
|
||||
|
||||
let result = TemplateBuilder::open(temp_file.path())
|
||||
.expect("Failed to open template")
|
||||
.add_var("user", user)
|
||||
.render()
|
||||
.expect("Failed to render template");
|
||||
|
||||
assert_eq!(result, "User: John Doe (john@example.com)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_builder_missing_variable_error() {
|
||||
let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
|
||||
let template_content = "Hello {{missing_var}}!";
|
||||
fs::write(temp_file.path(), template_content).expect("Failed to write template");
|
||||
|
||||
let result = TemplateBuilder::open(temp_file.path())
|
||||
.expect("Failed to open template")
|
||||
.render();
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_builder_invalid_template_syntax() {
|
||||
let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
|
||||
let template_content = "Hello {{unclosed_var!";
|
||||
fs::write(temp_file.path(), template_content).expect("Failed to write template");
|
||||
|
||||
let result = TemplateBuilder::open(temp_file.path())
|
||||
.expect("Failed to open template")
|
||||
.render();
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_builder_nonexistent_file() {
|
||||
let result = TemplateBuilder::open("/nonexistent/template.txt");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_builder_empty_template() {
|
||||
let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
|
||||
fs::write(temp_file.path(), "").expect("Failed to write empty template");
|
||||
|
||||
let result = TemplateBuilder::open(temp_file.path())
|
||||
.expect("Failed to open template")
|
||||
.render()
|
||||
.expect("Failed to render empty template");
|
||||
|
||||
assert_eq!(result, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_builder_template_with_no_variables() {
|
||||
let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
|
||||
let template_content = "This is a static template with no variables.";
|
||||
fs::write(temp_file.path(), template_content).expect("Failed to write template");
|
||||
|
||||
let result = TemplateBuilder::open(temp_file.path())
|
||||
.expect("Failed to open template")
|
||||
.render()
|
||||
.expect("Failed to render template");
|
||||
|
||||
assert_eq!(result, template_content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_builder_complex_report() {
|
||||
let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
|
||||
let template_content = r#"
|
||||
# {{report_title}}
|
||||
|
||||
Generated on: {{date}}
|
||||
|
||||
## Summary
|
||||
Total items: {{total_items}}
|
||||
Status: {{status}}
|
||||
|
||||
## Items
|
||||
{% for item in items %}
|
||||
- {{item.name}}: {{item.value}}{% if item.important %} (IMPORTANT){% endif %}
|
||||
{% endfor %}
|
||||
|
||||
## Footer
|
||||
{% if show_footer %}
|
||||
Report generated by {{generator}}
|
||||
{% endif %}
|
||||
"#;
|
||||
fs::write(temp_file.path(), template_content).expect("Failed to write template");
|
||||
|
||||
let mut item1 = HashMap::new();
|
||||
item1.insert("name".to_string(), "Item 1".to_string());
|
||||
item1.insert("value".to_string(), "100".to_string());
|
||||
item1.insert("important".to_string(), true.to_string());
|
||||
|
||||
let mut item2 = HashMap::new();
|
||||
item2.insert("name".to_string(), "Item 2".to_string());
|
||||
item2.insert("value".to_string(), "200".to_string());
|
||||
item2.insert("important".to_string(), false.to_string());
|
||||
|
||||
let items = vec![item1, item2];
|
||||
|
||||
let result = TemplateBuilder::open(temp_file.path())
|
||||
.expect("Failed to open template")
|
||||
.add_var("report_title", "Monthly Report")
|
||||
.add_var("date", "2023-12-01")
|
||||
.add_var("total_items", 2)
|
||||
.add_var("status", "Complete")
|
||||
.add_var("items", items)
|
||||
.add_var("show_footer", true)
|
||||
.add_var("generator", "SAL Text")
|
||||
.render()
|
||||
.expect("Failed to render template");
|
||||
|
||||
assert!(result.contains("# Monthly Report"));
|
||||
assert!(result.contains("Generated on: 2023-12-01"));
|
||||
assert!(result.contains("Total items: 2"));
|
||||
assert!(result.contains("- Item 1: 100"));
|
||||
assert!(result.contains("- Item 2: 200"));
|
||||
assert!(result.contains("Report generated by SAL Text"));
|
||||
}
|
159
text/tests/text_indentation_tests.rs
Normal file
159
text/tests/text_indentation_tests.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
//! Unit tests for text indentation functionality
|
||||
//!
|
||||
//! These tests validate the dedent and prefix functions including:
|
||||
//! - Common whitespace removal (dedent)
|
||||
//! - Line prefix addition (prefix)
|
||||
//! - Edge cases and special characters
|
||||
//! - Tab handling and mixed indentation
|
||||
|
||||
use sal_text::{dedent, prefix};
|
||||
|
||||
#[test]
|
||||
fn test_dedent_basic() {
|
||||
let indented = " line 1\n line 2\n line 3";
|
||||
let expected = "line 1\nline 2\n line 3";
|
||||
assert_eq!(dedent(indented), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dedent_empty_lines() {
|
||||
let indented = " line 1\n\n line 2\n line 3";
|
||||
let expected = "line 1\n\nline 2\n line 3";
|
||||
assert_eq!(dedent(indented), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dedent_tabs_as_spaces() {
|
||||
let indented = "\t\tline 1\n\t\tline 2\n\t\t\tline 3";
|
||||
let expected = "line 1\nline 2\n\tline 3";
|
||||
assert_eq!(dedent(indented), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dedent_mixed_tabs_and_spaces() {
|
||||
let indented = " \tline 1\n \tline 2\n \t line 3";
|
||||
let expected = "line 1\nline 2\n line 3";
|
||||
assert_eq!(dedent(indented), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dedent_no_common_indentation() {
|
||||
let text = "line 1\n line 2\n line 3";
|
||||
let expected = "line 1\n line 2\n line 3";
|
||||
assert_eq!(dedent(text), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dedent_single_line() {
|
||||
let indented = " single line";
|
||||
let expected = "single line";
|
||||
assert_eq!(dedent(indented), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dedent_empty_string() {
|
||||
assert_eq!(dedent(""), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dedent_only_whitespace() {
|
||||
let whitespace = " \n \n ";
|
||||
let expected = "\n\n";
|
||||
assert_eq!(dedent(whitespace), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prefix_basic() {
|
||||
let text = "line 1\nline 2\nline 3";
|
||||
let expected = " line 1\n line 2\n line 3";
|
||||
assert_eq!(prefix(text, " "), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prefix_with_symbols() {
|
||||
let text = "line 1\nline 2\nline 3";
|
||||
let expected = "> line 1\n> line 2\n> line 3";
|
||||
assert_eq!(prefix(text, "> "), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prefix_empty_lines() {
|
||||
let text = "line 1\n\nline 3";
|
||||
let expected = ">> line 1\n>> \n>> line 3";
|
||||
assert_eq!(prefix(text, ">> "), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prefix_single_line() {
|
||||
let text = "single line";
|
||||
let expected = "PREFIX: single line";
|
||||
assert_eq!(prefix(text, "PREFIX: "), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prefix_empty_string() {
|
||||
assert_eq!(prefix("", "PREFIX: "), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prefix_empty_prefix() {
|
||||
let text = "line 1\nline 2";
|
||||
assert_eq!(prefix(text, ""), text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dedent_and_prefix_combination() {
|
||||
let indented = " def function():\n print('hello')\n return True";
|
||||
let dedented = dedent(indented);
|
||||
let prefixed = prefix(&dedented, ">>> ");
|
||||
|
||||
let expected = ">>> def function():\n>>> print('hello')\n>>> return True";
|
||||
assert_eq!(prefixed, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dedent_real_code_example() {
|
||||
let code = r#"
|
||||
if condition:
|
||||
for item in items:
|
||||
process(item)
|
||||
return result
|
||||
else:
|
||||
return None"#;
|
||||
|
||||
let dedented = dedent(code);
|
||||
let expected = "\nif condition:\n for item in items:\n process(item)\n return result\nelse:\n return None";
|
||||
assert_eq!(dedented, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prefix_code_comment() {
|
||||
let code = "function main() {\n console.log('Hello');\n}";
|
||||
let commented = prefix(code, "// ");
|
||||
let expected = "// function main() {\n// console.log('Hello');\n// }";
|
||||
assert_eq!(commented, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dedent_preserves_relative_indentation() {
|
||||
let text = " start\n indented more\n back to start level\n indented again";
|
||||
let dedented = dedent(text);
|
||||
let expected = "start\n indented more\nback to start level\n indented again";
|
||||
assert_eq!(dedented, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prefix_with_unicode() {
|
||||
let text = "Hello 世界\nGoodbye 世界";
|
||||
let prefixed = prefix(text, "🔹 ");
|
||||
let expected = "🔹 Hello 世界\n🔹 Goodbye 世界";
|
||||
assert_eq!(prefixed, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dedent_with_unicode() {
|
||||
let text = " Hello 世界\n Goodbye 世界\n More indented 世界";
|
||||
let dedented = dedent(text);
|
||||
let expected = "Hello 世界\nGoodbye 世界\n More indented 世界";
|
||||
assert_eq!(dedented, expected);
|
||||
}
|
301
text/tests/text_replacement_tests.rs
Normal file
301
text/tests/text_replacement_tests.rs
Normal file
@@ -0,0 +1,301 @@
|
||||
//! Unit tests for text replacement functionality
|
||||
//!
|
||||
//! These tests validate the TextReplacer and TextReplacerBuilder 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, TextReplacerBuilder};
|
||||
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 mut 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 mut 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 mut 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));
|
||||
}
|
Reference in New Issue
Block a user