use ass_editor::*;
#[test]
fn test_empty_document_operations() {
let mut doc = EditorDocument::new();
assert_eq!(doc.len_bytes(), 0);
assert_eq!(doc.len_lines(), 1); assert!(doc.is_empty());
assert_eq!(doc.text(), "");
assert!(doc.undo().is_err());
assert!(doc.redo().is_err());
assert!(doc
.delete(Range::new(Position::new(0), Position::new(1)))
.is_err());
let result = doc.replace(Range::new(Position::new(0), Position::new(0)), "Hello");
assert!(result.is_ok());
assert_eq!(doc.text(), "Hello");
}
#[test]
fn test_boundary_position_operations() {
let mut doc = EditorDocument::from_content("Hello\nWorld").unwrap();
let len = doc.len_bytes();
doc.insert(Position::new(len), "!").unwrap();
assert_eq!(doc.text(), "Hello\nWorld!");
assert!(doc.insert(Position::new(len + 100), "X").is_err());
assert!(doc
.delete(Range::new(Position::new(len), Position::new(len + 10)))
.is_err());
let text_before = doc.text();
doc.delete(Range::new(Position::new(5), Position::new(5)))
.unwrap();
assert_eq!(doc.text(), text_before);
}
#[test]
fn test_unicode_and_special_characters() {
let test_cases = vec![
"Hello 世界", "Привет мир", "🎭🎬🎪", "नमस्ते", "مرحبا", "🇺🇸🇯🇵🇫🇷", "a\u{0301}", "👨👩👧👦", "\r\n\r\n", "\t\t\t", ];
for text in test_cases {
let mut doc = EditorDocument::from_content(text).unwrap();
assert_eq!(doc.text(), text);
doc.insert(Position::new(0), "X").unwrap();
assert!(doc.text().starts_with("X"));
assert_eq!(doc.text(), format!("X{text}"));
doc.undo().unwrap();
assert_eq!(doc.text(), text);
doc.insert(Position::new(0), "→").unwrap();
assert!(doc.text().starts_with("→"));
let expected = format!("→{text}");
assert_eq!(doc.text(), expected);
doc.undo().unwrap();
let after_undo = doc.text();
assert!(!after_undo.starts_with("→"));
}
}
#[test]
fn test_line_ending_handling() {
let endings = vec![
("Unix\nStyle\n", 3),
("Windows\r\nStyle\r\n", 3),
("Mixed\nLine\r\nEndings\r\n", 4),
("No newline at end", 1),
("\n\n\n", 4), ];
for (text, expected_lines) in endings {
let doc = EditorDocument::from_content(text).unwrap();
assert_eq!(doc.len_lines(), expected_lines);
assert_eq!(doc.text(), text);
}
}
#[test]
fn test_large_document_operations() {
let mut content = String::new();
for i in 0..1000 {
content.push_str(&format!(
"Line {i}: This is a test line with some content\n"
));
}
let mut doc = EditorDocument::from_content(&content).unwrap();
let original_len = doc.len_bytes();
doc.insert(Position::new(0), "START\n").unwrap();
doc.insert(Position::new(doc.len_bytes()), "\nEND").unwrap();
doc.insert(Position::new(original_len / 2), "\nMIDDLE\n")
.unwrap();
assert!(doc.text().starts_with("START\n"));
assert!(doc.text().ends_with("\nEND"));
assert!(doc.text().contains("\nMIDDLE\n"));
doc.undo().unwrap();
doc.undo().unwrap();
doc.undo().unwrap();
assert_eq!(doc.len_bytes(), original_len);
}
#[test]
fn test_position_edge_cases() {
let doc = EditorDocument::from_content("Hello\nWorld\n").unwrap();
let test_positions = vec![
(0, 1, 1), (5, 1, 6), (6, 2, 1), (11, 2, 6), (12, 3, 1), ];
for (offset, expected_line, expected_col) in test_positions {
let pos = Position::new(offset);
let lc = doc.position_to_line_column(pos).unwrap();
assert_eq!(lc.line, expected_line, "offset {offset} line mismatch");
assert_eq!(lc.column, expected_col, "offset {offset} column mismatch");
}
assert!(doc.position_to_line_column(Position::new(1000)).is_err());
}
#[test]
fn test_range_validation() {
let doc = EditorDocument::from_content("Hello World").unwrap();
assert!(doc
.text_range(Range::new(Position::new(0), Position::new(5)))
.is_ok());
assert!(doc
.text_range(Range::new(Position::new(6), Position::new(11)))
.is_ok());
let normalized_range = Range::new(Position::new(10), Position::new(5));
assert!(doc.text_range(normalized_range).is_ok());
assert_eq!(normalized_range.start.offset, 5);
assert_eq!(normalized_range.end.offset, 10);
assert!(doc
.text_range(Range::new(Position::new(0), Position::new(100)))
.is_err());
assert!(doc
.text_range(Range::new(Position::new(100), Position::new(200)))
.is_err());
}
#[test]
fn test_undo_redo_limits() {
let mut doc = EditorDocument::from_content("Start").unwrap();
for i in 0..100 {
doc.insert(Position::new(doc.len_bytes()), &format!("\nLine {i}"))
.unwrap();
}
let mut undo_count = 0;
while doc.undo().is_ok() {
undo_count += 1;
if undo_count > 200 {
panic!("Undo loop detected");
}
}
assert!(undo_count <= 50);
eprintln!("Undo count: {undo_count}");
eprintln!("Document text length: {}", doc.text().len());
eprintln!("Lines in doc: {}", doc.text().lines().count());
assert!(undo_count > 0);
let mut redo_count = 0;
while doc.redo().is_ok() {
redo_count += 1;
if redo_count > 200 {
panic!("Redo loop detected");
}
}
eprintln!("Redo count: {redo_count}");
assert!(redo_count > 0 && redo_count <= undo_count);
}
#[test]
fn test_undo_after_navigation() {
let mut doc = EditorDocument::from_content("Line 1\nLine 2\nLine 3").unwrap();
doc.insert(Position::new(6), " modified").unwrap();
let after_insert = doc.text();
let _ = doc.position_to_line_column(Position::new(0));
let _ = doc.text_range(Range::new(Position::new(0), Position::new(5)));
let _ = doc.len_lines();
doc.undo().unwrap();
assert_ne!(doc.text(), after_insert);
}
#[test]
fn test_ass_format_validation() {
let valid_cases = vec![
"[Script Info]\nTitle: Test",
"[Script Info]\n\n[Events]\n",
"[Script Info]\n[V4+ Styles]\n[Events]\n",
];
for content in valid_cases {
assert!(EditorDocument::from_content(content).is_ok());
}
let edge_cases = vec![
"", "\n\n\n", "[Script Info]", "Random text\n[Script Info]\n", ];
for content in edge_cases {
let result = EditorDocument::from_content(content);
if result.is_ok() {
let doc = result.unwrap();
assert_eq!(doc.text(), content);
}
}
}
#[test]
fn test_event_line_edge_cases() {
let content = r#"[Script Info]
Title: Test
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.00,0:00:01.00,Default,,0,0,0,,Simple text
Comment: 0,0:00:01.00,0:00:02.00,Default,,0,0,0,,This is a comment
Dialogue: 1,0:00:02.00,0:00:03.00,Default,,0,0,0,,{\an8}Text with tags
Dialogue: 0,0:00:03.00,0:00:04.00,Default,,0,0,0,,Line with\Nhard break
Dialogue: 0,0:00:04.00,0:00:05.00,Default,,0,0,0,,Special chars: <>&"'
"#;
let doc = EditorDocument::from_content(content).unwrap();
assert!(doc.text().contains("Simple text"));
assert!(doc.text().contains("This is a comment"));
assert!(doc.text().contains(r"{\an8}"));
assert!(doc.text().contains(r"\N"));
assert!(doc.text().contains("<>&\"'"));
}
#[test]
fn test_operation_atomicity() {
let mut doc = EditorDocument::from_content("Original").unwrap();
let original_text = doc.text();
let result = doc.delete(Range::new(Position::new(5), Position::new(100)));
assert!(result.is_err());
assert_eq!(doc.text(), original_text);
let result = doc.insert(Position::new(100), "Test");
assert!(result.is_err());
assert_eq!(doc.text(), original_text);
}
#[test]
fn test_undo_consistency_after_errors() {
let mut doc = EditorDocument::from_content("Start").unwrap();
doc.insert(Position::new(5), " Middle").unwrap();
assert!(doc.insert(Position::new(100), " Bad").is_err());
doc.insert(Position::new(doc.len_bytes()), " End").unwrap();
doc.undo().unwrap();
assert_eq!(doc.text(), "Start Middle");
doc.undo().unwrap();
assert_eq!(doc.text(), "Start");
}
#[test]
fn test_extension_manager_edge_cases() {
let mut manager = ExtensionManager::new();
let _ext_count_before = manager.list_extensions().len();
assert_eq!(manager.get_extension_state("non-existent"), None);
manager.set_config("key1".to_string(), "value1".to_string());
assert_eq!(manager.get_config("key1").as_deref(), Some("value1"));
manager.set_config("key1".to_string(), "value2".to_string());
assert_eq!(manager.get_config("key1").as_deref(), Some("value2"));
manager.set_config("".to_string(), "empty_key".to_string());
assert_eq!(manager.get_config("").as_deref(), Some("empty_key"));
manager.set_config("empty_value".to_string(), "".to_string());
assert_eq!(manager.get_config("empty_value").as_deref(), Some(""));
}
#[cfg(all(feature = "multi-thread", feature = "std"))]
mod concurrent_tests {
use super::*;
use std::sync::{Arc, Barrier};
use std::thread;
#[test]
fn test_concurrent_config_access() {
use std::sync::Arc;
let manager = Arc::new(ExtensionManager::new());
let barrier = Arc::new(Barrier::new(10));
let mut handles = vec![];
for i in 0..10 {
let manager_ref = Arc::clone(&manager);
let barrier_clone = barrier.clone();
let handle = thread::spawn(move || {
barrier_clone.wait();
for j in 0..100 {
let key = format!("thread_{i}_key_{j}");
let _value = manager_ref.get_config(&key);
}
});
handles.push(handle);
}
for i in 0..10 {
for j in 0..10 {
let _key = format!("thread_{i}_key_{j}");
}
}
for handle in handles {
handle.join().unwrap();
}
}
#[cfg(feature = "std")]
#[test]
fn test_concurrent_session_operations() {
use std::sync::atomic::{AtomicUsize, Ordering};
let _manager = EditorSessionManager::new();
let counter = Arc::new(AtomicUsize::new(0));
let mut handles = vec![];
for i in 0..5 {
let counter_clone = counter.clone();
let handle = thread::spawn(move || {
let mut local_manager = EditorSessionManager::new();
for j in 0..20 {
let session_name = format!("session_{i}_{j}");
let result = local_manager.create_session(session_name.clone());
if result.is_ok() {
local_manager
.with_document_mut(&session_name, |doc| {
doc.insert(Position::new(0), &format!("Thread {i} Doc {j}\n"))
})
.unwrap();
counter_clone.fetch_add(1, Ordering::Relaxed);
}
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let created_count = counter.load(Ordering::Relaxed);
assert!(created_count > 0);
}
}
#[test]
fn test_large_undo_history() {
let mut doc = EditorDocument::new();
for i in 0..1000 {
doc.insert(Position::new(0), &format!("{i}")).unwrap();
}
let mut actual_undos = 0;
for _ in 0..500 {
if doc.undo().is_ok() {
actual_undos += 1;
} else {
break;
}
}
assert!(actual_undos <= 50);
doc.insert(Position::new(0), "NEW").unwrap();
assert!(doc.redo().is_err());
}
#[test]
fn test_pathological_replace_patterns() {
let mut doc = EditorDocument::from_content("aaaaaaaaaaaaaaaaaaaa").unwrap();
doc.replace(
Range::new(Position::new(0), Position::new(20)),
"bbbbbbbbbbbbbbbbbbbb",
)
.unwrap();
assert_eq!(doc.text(), "bbbbbbbbbbbbbbbbbbbb");
doc.replace(
Range::new(Position::new(0), Position::new(20)),
&"c".repeat(1000),
)
.unwrap();
assert_eq!(doc.text().len(), 1000);
doc.replace(Range::new(Position::new(0), Position::new(1000)), "")
.unwrap();
assert_eq!(doc.text(), "");
}
#[test]
fn test_position_advance_edge_cases() {
let test_cases = vec![
("a", 1),
("é", 2), ("中", 3), ("𝄞", 4), ("\r\n", 2), ("\n", 1), ("\t", 1), ];
for (ch, expected_advance) in test_cases {
let pos = Position::new(0);
let new_pos = pos.advance(ch.len());
assert_eq!(new_pos.offset - pos.offset, expected_advance);
}
}