use super::*;
type Result<T = ()> = core::result::Result<T, Box<dyn std::error::Error>>;
fn format_change_block(search: &str, replace: &str) -> String {
format!("{LINE_MARKER_SEARCH_START}\n{search}\n{LINE_MARKER_SEP}\n{replace}\n{LINE_MARKER_REPLACE_END}")
}
fn format_multiple_change_blocks(blocks: Vec<(&str, &str)>, separator_str: &str) -> String {
blocks
.into_iter()
.map(|(s, r)| format_change_block(s, r))
.collect::<Vec<String>>()
.join(separator_str)
}
#[test]
fn test_support_text_apply_change_simple_replace_no_markers() -> Result {
let original = "Hello world";
let changes = "Hallo Welt".to_string();
let result = apply_changes(original, changes)?.0;
assert_eq!(result, "Hallo Welt");
Ok(())
}
#[test]
fn test_support_text_apply_change_simple_replace_contains_end_marker_only_as_text() -> Result {
let original = "Hello world";
let changes = format!("Some text {LINE_MARKER_REPLACE_END} with an end marker style part");
let result = apply_changes(original, changes.clone())?.0;
assert_eq!(result, changes);
Ok(())
}
#[test]
fn test_support_text_apply_change_single_valid_block_original_markers_format() -> Result {
let original = "Hello old_text world, and old_text again.";
let changes = format_change_block("old_text", "new_text");
let result = apply_changes(original, changes)?.0;
assert_eq!(result, "Hello new_text world, and old_text again.");
Ok(())
}
#[test]
fn test_support_text_apply_change_single_valid_block_explicit_lines() -> Result {
let original = "Hello old_text1 world.";
let changes = format_change_block("old_text1", "new_text1");
let result = apply_changes(original, changes)?.0;
assert_eq!(result, "Hello new_text1 world.");
Ok(())
}
#[test]
fn test_support_text_apply_change_multiline_patterns_in_block_new_format() -> Result {
let original = "First line\nSecond old line\nThird line";
let search_pattern_text = "Second old line";
let replace_pattern_text = "Second new line\nAnd a new third line";
let changes = format_change_block(search_pattern_text, replace_pattern_text);
let result = apply_changes(original, changes)?.0;
let expected = "First line\nSecond new line\nAnd a new third line\nThird line";
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_support_text_apply_change_multiple_valid_blocks() -> Result {
let original = "one two three two one";
let blocks_data = vec![("one", "1"), ("two", "2"), ("three", "3")];
let changes = format_multiple_change_blocks(blocks_data, "\n");
let result = apply_changes(original, changes)?.0;
assert_eq!(result, "1 2 3 two one");
Ok(())
}
#[test]
fn test_support_text_apply_change_multiple_blocks_with_newlines_between() -> Result {
let original = "one two three two one";
let blocks_data = vec![("one", "1"), ("two", "2"), ("three", "3")];
let changes = format_multiple_change_blocks(blocks_data, "\n\n\n");
let result = apply_changes(original, changes)?.0;
assert_eq!(result, "1 2 3 two one"); Ok(())
}
#[test]
fn test_support_text_apply_change_multiple_blocks_with_mixed_whitespace_and_trailing_clean_markers() -> Result {
let original = "apple banana cherry";
let block1 = format_change_block("apple", "A");
let block2 = format_change_block("banana", "B");
let block3 = format_change_block("cherry", "C");
let changes = format!("{block1}\n{block2}\n{block3}");
let result = apply_changes(original, changes)?.0;
assert_eq!(result, "A B C");
Ok(())
}
#[test]
fn test_support_text_apply_change_error_marker_line_with_trailing_whitespace() -> Result {
let original = "apple";
let changes = format!(
"{}\napple\n{}\n{}\n{}",
LINE_MARKER_SEARCH_START,
LINE_MARKER_SEP,
"A",
LINE_MARKER_REPLACE_END )
.replace(LINE_MARKER_SEP, &format!("{LINE_MARKER_SEP} "));
let result = apply_changes(original, changes);
assert!(result.is_err(), "Should fail due to malformed marker line");
if let Err(e) = result {
assert!(
e.to_string().contains(&format!("Missing separator marker '{LINE_MARKER_SEP}'")),
"Error message mismatch: {e}"
);
}
Ok(())
}
#[test]
fn test_support_text_apply_change_error_with_preamble() -> Result {
let original = "Replace target.";
let changes = format!(
"Some preamble text, should cause error.\n{LINE_MARKER_SEARCH_START}\ntarget\n{LINE_MARKER_SEP}\nreplacement\n{LINE_MARKER_REPLACE_END}"
);
let result = apply_changes(original, changes);
assert!(result.is_err(), "Should fail due to preamble text");
if let Err(e) = result {
assert!(
e.to_string().contains("Expected '<<<<<<< SEARCH' or a whitespace line"),
"Error message mismatch: {e}"
);
assert!(
e.to_string().contains("Line: 'Some preamble text"),
"Error message mismatch: {e}"
);
}
Ok(())
}
#[test]
fn test_support_text_apply_change_error_with_interstitial_text() -> Result {
let original = "one two";
let block1 = format_change_block("one", "1");
let block2 = format_change_block("two", "2");
let changes = format!("{block1}\n Some interstitial text, should cause error.\n{block2}");
let result = apply_changes(original, changes);
assert!(result.is_err(), "Should fail due to interstitial text");
if let Err(e) = result {
assert!(
e.to_string().contains("Expected '<<<<<<< SEARCH' or a whitespace line"),
"Error message mismatch: {e}"
);
assert!(
e.to_string().contains("Line: ' Some interstitial text"),
"Error message mismatch: {e}"
);
}
Ok(())
}
#[test]
fn test_support_text_apply_change_error_with_trailing_text() -> Result {
let original = "Replace target.";
let block = format_change_block("target", "replacement");
let changes = format!("{block}\n Some trailing text, should cause error.");
let result = apply_changes(original, changes);
assert!(result.is_err(), "Should fail due to trailing text");
if let Err(e) = result {
assert!(
e.to_string().contains("Expected '<<<<<<< SEARCH' or a whitespace line"),
"Error message mismatch: {e}"
);
assert!(
e.to_string().contains("Line: ' Some trailing text"),
"Error message mismatch: {e}"
);
}
Ok(())
}
#[test]
fn test_support_text_apply_change_search_not_found_in_original() -> Result {
let original = "Hello world";
let changes = format_change_block("not_found_text", "replacement");
let (result, info) = apply_changes(original, changes)?;
assert_eq!(
result, original,
"Original should be unchanged as search pattern not found"
);
assert_eq!(info.changed_count, 0);
assert_eq!(info.failed_changes.len(), 1);
assert_eq!(info.failed_changes[0].search, "not_found_text");
assert_eq!(info.failed_changes[0].reason, "Search block not found in content");
Ok(())
}
#[test]
fn test_support_text_apply_change_mixed_success_and_failure() -> Result {
let original = "one two three";
let changes = format!(
"{}\n{}",
format_change_block("two", "2"), format_change_block("four", "4") );
let (result, info) = apply_changes(original, changes)?;
assert_eq!(result, "one 2 three");
assert_eq!(info.changed_count, 1);
assert_eq!(info.failed_changes.len(), 1);
assert_eq!(info.failed_changes[0].search, "four");
assert_eq!(info.failed_changes[0].reason, "Search block not found in content");
Ok(())
}
#[test]
fn test_support_text_apply_change_empty_search_pattern() -> Result {
let original = "abc";
let changes = format_change_block("", "X");
let result = apply_changes(original, changes)?.0;
assert_eq!(result, "Xabc");
Ok(())
}
#[test]
fn test_support_text_apply_change_empty_replace_pattern() -> Result {
let original = "delete this text now";
let changes = format_change_block("this text", "");
let result = apply_changes(original, changes)?.0;
assert_eq!(result, "delete now");
Ok(())
}
#[test]
fn test_support_text_apply_change_empty_replace_pattern_removes_line_and_newline() -> Result {
let original = "line one\nline two\nline three\n";
let changes = format_change_block("line two", "");
let result = apply_changes(original, changes)?.0;
assert_eq!(result, "line one\nline three\n");
Ok(())
}
#[test]
fn test_support_text_apply_change_malformed_missing_separator() -> Result {
let original = "Hello world";
let changes = format!(
"{LINE_MARKER_SEARCH_START}\nsearch_text_no_sep_then_end\n{LINE_MARKER_REPLACE_END}" );
let result = apply_changes(original, changes);
assert!(result.is_err(), "Should fail due to missing separator");
if let Err(e) = result {
assert!(
e.to_string().contains(&format!("Missing separator marker '{LINE_MARKER_SEP}'")),
"Error message mismatch: {e}"
);
}
Ok(())
}
#[test]
fn test_support_text_apply_change_malformed_missing_end() -> Result {
let original = "Hello world";
let changes = format!(
"{LINE_MARKER_SEARCH_START}\nsearch_text\n{LINE_MARKER_SEP}\nreplace_text_no_end" );
let result = apply_changes(original, changes);
assert!(result.is_err(), "Should fail due to missing end marker");
if let Err(e) = result {
assert!(
e.to_string()
.contains(&format!("Missing end marker '{LINE_MARKER_REPLACE_END}'")),
"Error message mismatch: {e}"
);
}
Ok(())
}
#[test]
fn test_support_text_apply_change_markers_as_text_in_patterns() -> Result {
let original = format!(
"Content with {LINE_MARKER_SEARCH_START} inside, and also {LINE_MARKER_SEP} and {LINE_MARKER_REPLACE_END}."
);
let search_pattern = LINE_MARKER_SEARCH_START; let replace_pattern = "FOUND_IT";
let changes = format_change_block(search_pattern, replace_pattern);
let result = apply_changes(original.as_str(), changes)?.0;
assert_eq!(
result,
format!(
"Content with FOUND_IT inside, and also {LINE_MARKER_SEP} and {LINE_MARKER_REPLACE_END}.", )
);
Ok(())
}
#[test]
fn test_support_text_apply_change_block_at_eof_no_trailing_newline_in_changes_input() -> Result {
let original = "fix this";
let changes_str_no_final_newline = format_change_block("this", "that");
let result = apply_changes(original, changes_str_no_final_newline)?.0;
assert_eq!(result, "fix that");
Ok(())
}
#[test]
fn test_support_text_apply_change_empty_changes_string() -> Result {
let original = "original content";
let changes = "".to_string();
let result = apply_changes(original, changes)?.0;
assert_eq!(result, "");
Ok(())
}
#[test]
fn test_support_text_apply_change_changes_is_just_search_start_marker_line() -> Result {
let original = "original";
let changes = LINE_MARKER_SEARCH_START.to_string();
let result = apply_changes(original, changes);
assert!(result.is_err());
if let Err(e) = result {
assert!(e.to_string().contains(&format!("Missing separator marker '{LINE_MARKER_SEP}'")));
}
Ok(())
}
#[test]
fn test_support_text_apply_change_changes_is_incomplete_block_missing_replace_text_and_end_marker() -> Result {
let original = "original";
let changes = format!("{LINE_MARKER_SEARCH_START}\nsearch_text\n{LINE_MARKER_SEP}");
let result = apply_changes(original, changes);
assert!(result.is_err());
if let Err(e) = result {
assert!(
e.to_string()
.contains(&format!("Missing end marker '{LINE_MARKER_REPLACE_END}'"))
);
}
Ok(())
}
#[test]
fn test_support_text_apply_change_empty_search_pattern_in_block() -> Result {
let original = "abc def";
let changes = format_change_block("", "X-");
let result = apply_changes(original, changes)?.0;
assert_eq!(result, "X-abc def");
Ok(())
}
#[test]
fn test_support_text_apply_change_empty_replace_pattern_in_block() -> Result {
let original = "abc remove_this def";
let changes = format_change_block("remove_this", "");
let result = apply_changes(original, changes)?.0;
assert_eq!(result, "abc def");
Ok(())
}
#[test]
fn test_support_text_apply_change_multiline_search_and_replace() -> Result {
let original = "line one\nline two\nline three\nline four";
let search = "line two\nline three";
let replace = "new line A\nnew line B";
let changes = format_change_block(search, replace);
let result = apply_changes(original, changes)?.0;
assert_eq!(result, "line one\nnew line A\nnew line B\nline four");
Ok(())
}
#[test]
fn test_support_text_apply_change_crlf_windows() -> Result {
let original = "line one\nline two\nline three\nline four";
let original = original.replace("\n", "\r\n");
let search = "line two\nline three";
let replace = "new line A\nnew line B";
let changes = format_change_block(search, replace);
let result = apply_changes(&original, changes)?.0;
assert_eq!(result, "line one\nnew line A\nnew line B\nline four");
Ok(())
}