use ass_editor::*;
#[test]
fn test_invalid_utf8_handling() {
let edge_cases = vec![
"\u{0}", "\u{FEFF}test", "test\u{200B}ing", ];
for content in edge_cases {
let result = EditorDocument::from_content(content);
if let Ok(doc) = result {
assert_eq!(doc.text(), content);
}
}
}
#[test]
fn test_empty_and_whitespace_documents() {
let test_cases = vec![
"",
" ",
"\t",
"\n",
"\r\n",
" \n \n ",
"\t\t\t",
"\n\n\n\n\n",
];
for content in test_cases {
let doc = EditorDocument::from_content(content).unwrap();
assert_eq!(doc.text(), content);
let mut doc_mut = doc;
doc_mut.insert(Position::new(0), "X").unwrap();
assert!(doc_mut.text().starts_with('X'));
}
}
#[test]
fn test_invalid_position_operations() {
let mut doc = EditorDocument::from_content("Hello").unwrap();
assert!(doc.insert(Position::new(100), "X").is_err());
assert!(doc.position_to_line_column(Position::new(100)).is_err());
let huge_pos = Position::new(usize::MAX);
assert!(doc.insert(huge_pos, "X").is_err());
assert!(doc.insert(Position::new(5), " World").is_ok());
}
#[test]
fn test_invalid_range_operations() {
let mut doc = EditorDocument::from_content("Hello World").unwrap();
let test_ranges = [
Range::new(Position::new(5), Position::new(0)),
Range::new(Position::new(100), Position::new(200)),
Range::new(Position::new(5), Position::new(100)),
Range::new(Position::new(0), Position::new(usize::MAX)),
];
for (idx, range) in test_ranges.iter().enumerate() {
if idx == 0 {
assert_eq!(range.start.offset, 0);
assert_eq!(range.end.offset, 5);
assert!(doc.delete(*range).is_ok());
doc.undo().unwrap();
continue;
}
assert!(
doc.delete(*range).is_err(),
"delete should fail for range {range:?}"
);
assert!(
doc.text_range(*range).is_err(),
"text_range should fail for range {range:?}"
);
assert!(
doc.replace(*range, "X").is_err(),
"replace should fail for range {range:?}"
);
}
}
#[test]
fn test_line_column_errors() {
let doc = EditorDocument::from_content("Line1\nLine2\nLine3").unwrap();
let invalid_positions = vec![
Position::new(1000), Position::new(usize::MAX),
];
for pos in invalid_positions {
assert!(doc.position_to_line_column(pos).is_err());
}
assert!(ass_editor::core::LineColumn::new(0, 1).is_err()); assert!(ass_editor::core::LineColumn::new(1, 0).is_err()); }
#[test]
fn test_failed_operations_dont_modify_document() {
let original = "Original content";
let mut doc = EditorDocument::from_content(original).unwrap();
let insert_result = doc.insert(Position::new(1000), "Should not appear");
assert!(insert_result.is_err());
assert_eq!(doc.text(), original);
let delete_result = doc.delete(Range::new(Position::new(0), Position::new(1000)));
assert!(delete_result.is_err());
assert_eq!(doc.text(), original);
let replace_result = doc.replace(
Range::new(Position::new(8), Position::new(1000)),
"Should not appear",
);
assert!(replace_result.is_err());
assert_eq!(doc.text(), original);
}
#[test]
fn test_undo_redo_consistency_with_errors() {
let mut doc = EditorDocument::from_content("Step 1").unwrap();
doc.insert(Position::new(6), " - Success").unwrap();
assert_eq!(doc.text(), "Step 1 - Success");
assert!(doc.insert(Position::new(1000), " - Fail").is_err());
doc.insert(Position::new(doc.len_bytes()), " - Step 2")
.unwrap();
doc.undo().unwrap();
assert_eq!(doc.text(), "Step 1 - Success");
doc.undo().unwrap();
assert_eq!(doc.text(), "Step 1");
doc.redo().unwrap();
assert_eq!(doc.text(), "Step 1 - Success");
let redo_result = doc.redo();
if redo_result.is_ok() {
assert_eq!(doc.text(), "Step 1 - Success - Step 2");
} else {
assert_eq!(doc.text(), "Step 1 - Success");
}
}
#[test]
fn test_extension_manager_error_cases() {
let mut manager = ExtensionManager::new();
assert_eq!(manager.get_extension_state("non-existent"), None);
manager.set_config("".to_string(), "value".to_string());
assert_eq!(manager.get_config("").as_deref(), Some("value"));
manager.set_config("key".to_string(), "".to_string());
assert_eq!(manager.get_config("key").as_deref(), Some(""));
let long_key = "k".repeat(10000);
let long_value = "v".repeat(10000);
manager.set_config(long_key.clone(), long_value.clone());
assert_eq!(
manager.get_config(&long_key).as_deref(),
Some(long_value.as_str())
);
}
#[test]
#[cfg(feature = "std")] fn test_session_manager_error_cases() {
let mut manager = EditorSessionManager::new();
manager.create_session("test".to_string()).unwrap();
let _duplicate_result = manager.create_session("test".to_string());
assert!(manager.remove_session("non-existent").is_err());
assert!(manager
.with_document_mut("non-existent", |_| Ok(()))
.is_err());
let empty_result = manager.create_session("".to_string());
if empty_result.is_ok() {
manager.remove_session("").unwrap();
}
let long_name = "x".repeat(10000);
let result = manager.create_session(long_name.clone());
if result.is_ok() {
assert!(manager.list_sessions().unwrap().contains(&long_name));
}
}
#[cfg(all(feature = "multi-thread", feature = "std"))]
#[test]
fn test_concurrent_error_handling() {
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
let error_count = Arc::new(AtomicUsize::new(0));
let success_count = Arc::new(AtomicUsize::new(0));
let mut handles = vec![];
for i in 0..10 {
let error_count_clone = error_count.clone();
let success_count_clone = success_count.clone();
let handle = thread::spawn(move || {
let mut local_manager = ass_editor::EditorSessionManager::new();
match local_manager.create_session("shared".to_string()) {
Ok(_) => success_count_clone.fetch_add(1, Ordering::Relaxed),
Err(_) => error_count_clone.fetch_add(1, Ordering::Relaxed),
};
let result = local_manager.with_document_mut(&format!("missing_{i}"), |_doc| Ok(()));
match result {
Ok(_) => success_count_clone.fetch_add(1, Ordering::Relaxed),
Err(_) => error_count_clone.fetch_add(1, Ordering::Relaxed),
};
match local_manager.create_session(format!("thread_{i}")) {
Ok(_) => success_count_clone.fetch_add(1, Ordering::Relaxed),
Err(_) => error_count_clone.fetch_add(1, Ordering::Relaxed),
};
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
assert!(error_count.load(Ordering::Relaxed) > 0);
assert!(success_count.load(Ordering::Relaxed) > 0);
}
#[test]
fn test_document_size_limits() {
let sizes = vec![
1_000_000, 10_000_000, ];
for size in sizes {
let content = "X".repeat(size);
let result = EditorDocument::from_content(&content);
if let Ok(mut doc) = result {
assert_eq!(doc.len_bytes(), size);
let double_result = doc.insert(Position::new(0), &content);
if double_result.is_ok() {
assert_eq!(doc.len_bytes(), size * 2);
}
}
}
}
#[test]
fn test_undo_stack_memory_pressure() {
let mut doc = EditorDocument::new();
for i in 0..10000 {
doc.insert(Position::new(0), &i.to_string()).unwrap();
}
let mut undo_count = 0;
while doc.undo().is_ok() && undo_count < 5000 {
undo_count += 1;
}
let large_text = "X".repeat(1_000_000);
let result = doc.insert(Position::new(0), &large_text);
if result.is_ok() {
assert!(doc.redo().is_err());
}
}
#[test]
fn test_parser_recovery_from_corruption() {
let nested_tags = format!("[Events]\nDialogue: {}", "{".repeat(1000));
let corrupted_cases = vec![
"[\0Script Info]\nTitle: Test",
"[Script Info]\nTitle: Test\n[Eve",
nested_tags.as_str(),
"[Script Info\nTitle: Test\n[Events",
];
for content in corrupted_cases {
let result = EditorDocument::from_content(content);
match result {
Ok(doc) => {
let _ = doc.text();
let mut doc_mut = doc;
let _ = doc_mut.insert(Position::new(0), "X");
}
Err(_) => {
}
}
}
}
#[test]
fn test_document_state_consistency_after_errors() {
let mut doc = EditorDocument::from_content("Initial").unwrap();
let initial_len = doc.len_bytes();
let initial_lines = doc.len_lines();
doc.insert(Position::new(7), " state").unwrap();
assert!(doc.insert(Position::new(1000), "fail").is_err());
doc.delete(Range::new(Position::new(0), Position::new(7)))
.unwrap();
assert!(doc
.delete(Range::new(Position::new(0), Position::new(1000)))
.is_err());
assert_eq!(doc.text(), " state");
assert_eq!(doc.len_bytes(), 6);
assert_eq!(doc.len_lines(), 1);
doc.undo().unwrap();
doc.undo().unwrap();
assert_eq!(doc.text(), "Initial");
assert_eq!(doc.len_bytes(), initial_len);
assert_eq!(doc.len_lines(), initial_lines);
}