extern crate text_document_editing as document_editing;
use anyhow::Result;
use test_harness::{export_text, setup_with_text};
use document_editing::document_editing_controller;
use document_editing::{DeleteTextDto, InsertBlockDto, InsertTextDto};
fn backspace(
db_context: &common::database::db_context::DbContext,
event_hub: &std::sync::Arc<common::event::EventHub>,
undo_redo_manager: &mut common::undo_redo::UndoRedoManager,
cursor_pos: i64,
) -> Result<i64> {
let result = document_editing_controller::delete_text(
db_context,
event_hub,
undo_redo_manager,
None,
&DeleteTextDto {
position: cursor_pos,
anchor: cursor_pos - 1,
},
)?;
Ok(result.new_position)
}
fn forward_delete(
db_context: &common::database::db_context::DbContext,
event_hub: &std::sync::Arc<common::event::EventHub>,
undo_redo_manager: &mut common::undo_redo::UndoRedoManager,
cursor_pos: i64,
) -> Result<i64> {
let result = document_editing_controller::delete_text(
db_context,
event_hub,
undo_redo_manager,
None,
&DeleteTextDto {
position: cursor_pos,
anchor: cursor_pos + 1,
},
)?;
Ok(result.new_position)
}
#[test]
fn test_insert_text_at_beginning() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
let result = document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 0,
anchor: 0,
text: "Say ".to_string(),
},
)?;
assert_eq!(result.new_position, 4);
assert_eq!(result.blocks_affected, 1);
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "Say Hello");
Ok(())
}
#[test]
fn test_insert_text_at_end() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
let result = document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 5,
anchor: 5,
text: " World".to_string(),
},
)?;
assert_eq!(result.new_position, 11);
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "Hello World");
Ok(())
}
#[test]
fn test_insert_text_in_middle() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Helo")?;
let result = document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 2,
anchor: 2,
text: "l".to_string(),
},
)?;
assert_eq!(result.new_position, 3);
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "Hello");
Ok(())
}
#[test]
fn test_delete_text_within_block() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello World")?;
let result = document_editing_controller::delete_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&DeleteTextDto {
position: 6,
anchor: 11,
},
)?;
assert_eq!(result.new_position, 6);
assert_eq!(result.deleted_text, "World");
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "Hello ");
Ok(())
}
#[test]
fn test_delete_text_noop_same_position() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
let result = document_editing_controller::delete_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&DeleteTextDto {
position: 3,
anchor: 3,
},
)?;
assert_eq!(result.new_position, 3);
assert_eq!(result.deleted_text, "");
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "Hello");
Ok(())
}
#[test]
fn test_insert_block_creates_new_block() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("HelloWorld")?;
let result = document_editing_controller::insert_block(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertBlockDto {
position: 5,
anchor: 5,
},
)?;
assert!(result.new_block_id > 0);
assert_eq!(result.new_position, 6);
let stats = test_harness::get_document_stats(&db_context)?;
assert_eq!(stats.block_count, 2);
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "Hello\nWorld");
Ok(())
}
#[test]
fn test_insert_text_unicode() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("café")?;
let result = document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 4, anchor: 4,
text: " latte".to_string(),
},
)?;
assert_eq!(result.new_position, 10); let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "café latte");
Ok(())
}
#[test]
fn test_insert_text_replaces_selection() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello World")?;
let result = document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 6,
anchor: 11,
text: "Rust".to_string(),
},
)?;
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "Hello Rust");
assert_eq!(result.new_position, 10);
Ok(())
}
#[test]
fn test_delete_text_reversed_range() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello World")?;
let result = document_editing_controller::delete_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&DeleteTextDto {
position: 11,
anchor: 6,
},
)?;
assert_eq!(result.new_position, 6);
assert_eq!(result.deleted_text, "World");
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "Hello ");
Ok(())
}
#[test]
fn test_delete_text_cross_block() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello\nWorld")?;
let result = document_editing_controller::delete_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&DeleteTextDto {
position: 3,
anchor: 9,
},
)?;
assert_eq!(result.new_position, 3);
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "Helld");
Ok(())
}
#[test]
fn test_delete_text_entire_content() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
let result = document_editing_controller::delete_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&DeleteTextDto {
position: 0,
anchor: 5,
},
)?;
assert_eq!(result.new_position, 0);
assert_eq!(result.deleted_text, "Hello");
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "");
Ok(())
}
#[test]
fn test_insert_block_at_start() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
let result = document_editing_controller::insert_block(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertBlockDto {
position: 0,
anchor: 0,
},
)?;
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "\nHello");
assert!(result.new_block_id > 0);
Ok(())
}
#[test]
fn test_insert_block_at_end() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
let result = document_editing_controller::insert_block(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertBlockDto {
position: 5,
anchor: 5,
},
)?;
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "Hello\n");
assert!(result.new_block_id > 0);
Ok(())
}
#[test]
fn test_insert_text_updates_stats() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hi")?;
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 2,
anchor: 2,
text: " there".to_string(),
},
)?;
let stats = test_harness::get_document_stats(&db_context)?;
assert_eq!(stats.character_count, 8); assert_eq!(stats.block_count, 1);
Ok(())
}
#[test]
fn test_insert_text_undo() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 5,
anchor: 5,
text: " World".to_string(),
},
)?;
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "Hello World");
undo_redo_manager.undo(None)?;
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "Hello");
undo_redo_manager.redo(None)?;
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "Hello World");
Ok(())
}
#[test]
fn test_delete_text_undo() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello World")?;
document_editing_controller::delete_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&DeleteTextDto {
position: 5,
anchor: 11,
},
)?;
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "Hello");
undo_redo_manager.undo(None)?;
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "Hello World");
Ok(())
}
#[test]
fn test_insert_block_undo() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("HelloWorld")?;
document_editing_controller::insert_block(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertBlockDto {
position: 5,
anchor: 5,
},
)?;
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "Hello\nWorld");
undo_redo_manager.undo(None)?;
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "HelloWorld");
Ok(())
}
#[test]
fn test_insert_text_merge_consecutive_chars() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 5,
anchor: 5,
text: "a".to_string(),
},
)?;
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 6,
anchor: 6,
text: "b".to_string(),
},
)?;
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 7,
anchor: 7,
text: "c".to_string(),
},
)?;
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "Helloabc");
assert_eq!(undo_redo_manager.get_stack_size(0), 1);
undo_redo_manager.undo(None)?;
let text = export_text(&db_context, &event_hub)?;
assert_eq!(text, "Hello");
Ok(())
}
#[test]
fn test_insert_text_merge_undo_redo() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
for (i, ch) in "World".chars().enumerate() {
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 5 + i as i64,
anchor: 5 + i as i64,
text: ch.to_string(),
},
)?;
}
assert_eq!(export_text(&db_context, &event_hub)?, "HelloWorld");
assert_eq!(undo_redo_manager.get_stack_size(0), 1);
undo_redo_manager.undo(None)?;
assert_eq!(export_text(&db_context, &event_hub)?, "Hello");
undo_redo_manager.redo(None)?;
assert_eq!(export_text(&db_context, &event_hub)?, "HelloWorld");
Ok(())
}
#[test]
fn test_insert_text_no_merge_non_contiguous() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 5,
anchor: 5,
text: "a".to_string(),
},
)?;
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 0,
anchor: 0,
text: "b".to_string(),
},
)?;
assert_eq!(export_text(&db_context, &event_hub)?, "bHelloa");
assert_eq!(undo_redo_manager.get_stack_size(0), 2);
undo_redo_manager.undo(None)?;
assert_eq!(export_text(&db_context, &event_hub)?, "Helloa");
undo_redo_manager.undo(None)?;
assert_eq!(export_text(&db_context, &event_hub)?, "Hello");
Ok(())
}
#[test]
fn test_insert_text_no_merge_with_selection() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 5,
anchor: 5,
text: "a".to_string(),
},
)?;
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 5,
anchor: 6,
text: "b".to_string(),
},
)?;
assert_eq!(export_text(&db_context, &event_hub)?, "Hellob");
assert_eq!(undo_redo_manager.get_stack_size(0), 2);
Ok(())
}
#[test]
fn test_insert_text_merge_word_boundary() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 5,
anchor: 5,
text: "a".to_string(),
},
)?;
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 6,
anchor: 6,
text: "b".to_string(),
},
)?;
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 7,
anchor: 7,
text: " ".to_string(),
},
)?;
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 8,
anchor: 8,
text: "c".to_string(),
},
)?;
assert_eq!(export_text(&db_context, &event_hub)?, "Helloab c");
assert_eq!(undo_redo_manager.get_stack_size(0), 2);
undo_redo_manager.undo(None)?;
assert_eq!(export_text(&db_context, &event_hub)?, "Helloab ");
undo_redo_manager.undo(None)?;
assert_eq!(export_text(&db_context, &event_hub)?, "Hello");
Ok(())
}
#[test]
fn test_insert_text_merge_max_length() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("")?;
let long_text: String = "x".repeat(200);
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 0,
anchor: 0,
text: long_text.clone(),
},
)?;
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 200,
anchor: 200,
text: "y".to_string(),
},
)?;
assert_eq!(undo_redo_manager.get_stack_size(0), 2);
Ok(())
}
#[test]
fn test_insert_text_no_merge_after_delete() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 5,
anchor: 5,
text: "a".to_string(),
},
)?;
document_editing_controller::delete_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&DeleteTextDto {
position: 5,
anchor: 6,
},
)?;
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 5,
anchor: 5,
text: "b".to_string(),
},
)?;
assert_eq!(export_text(&db_context, &event_hub)?, "Hellob");
assert_eq!(undo_redo_manager.get_stack_size(0), 3);
Ok(())
}
#[test]
fn test_insert_text_merge_punctuation_boundary() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 5,
anchor: 5,
text: "a".to_string(),
},
)?;
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 6,
anchor: 6,
text: ".".to_string(),
},
)?;
assert_eq!(undo_redo_manager.get_stack_size(0), 1);
document_editing_controller::insert_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertTextDto {
format_policy: Default::default(),
position: 7,
anchor: 7,
text: "b".to_string(),
},
)?;
assert_eq!(export_text(&db_context, &event_hub)?, "Helloa.b");
assert_eq!(undo_redo_manager.get_stack_size(0), 2);
undo_redo_manager.undo(None)?;
assert_eq!(export_text(&db_context, &event_hub)?, "Helloa.");
undo_redo_manager.undo(None)?;
assert_eq!(export_text(&db_context, &event_hub)?, "Hello");
Ok(())
}
#[test]
fn test_delete_text_merge_consecutive_backspaces() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
let pos = backspace(&db_context, &event_hub, &mut undo_redo_manager, 5)?;
assert_eq!(pos, 4);
let pos = backspace(&db_context, &event_hub, &mut undo_redo_manager, pos)?;
assert_eq!(pos, 3);
let _pos = backspace(&db_context, &event_hub, &mut undo_redo_manager, pos)?;
assert_eq!(export_text(&db_context, &event_hub)?, "He");
assert_eq!(undo_redo_manager.get_stack_size(0), 1);
undo_redo_manager.undo(None)?;
assert_eq!(export_text(&db_context, &event_hub)?, "Hello");
Ok(())
}
#[test]
fn test_delete_text_merge_consecutive_forward_deletes() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
let pos = forward_delete(&db_context, &event_hub, &mut undo_redo_manager, 1)?;
assert_eq!(pos, 1);
let pos = forward_delete(&db_context, &event_hub, &mut undo_redo_manager, pos)?;
assert_eq!(pos, 1);
let _pos = forward_delete(&db_context, &event_hub, &mut undo_redo_manager, pos)?;
assert_eq!(export_text(&db_context, &event_hub)?, "Ho");
assert_eq!(undo_redo_manager.get_stack_size(0), 1);
undo_redo_manager.undo(None)?;
assert_eq!(export_text(&db_context, &event_hub)?, "Hello");
Ok(())
}
#[test]
fn test_delete_text_merge_undo_redo() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
let mut pos = 5;
for _ in 0..3 {
pos = backspace(&db_context, &event_hub, &mut undo_redo_manager, pos)?;
}
assert_eq!(export_text(&db_context, &event_hub)?, "He");
undo_redo_manager.undo(None)?;
assert_eq!(export_text(&db_context, &event_hub)?, "Hello");
undo_redo_manager.redo(None)?;
assert_eq!(export_text(&db_context, &event_hub)?, "He");
Ok(())
}
#[test]
fn test_delete_text_no_merge_different_direction() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
let _pos = backspace(&db_context, &event_hub, &mut undo_redo_manager, 5)?;
let _pos = forward_delete(&db_context, &event_hub, &mut undo_redo_manager, 0)?;
assert_eq!(undo_redo_manager.get_stack_size(0), 2);
Ok(())
}
#[test]
fn test_delete_text_no_merge_non_contiguous() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello World")?;
let _pos = backspace(&db_context, &event_hub, &mut undo_redo_manager, 11)?;
let _pos = backspace(&db_context, &event_hub, &mut undo_redo_manager, 5)?;
assert_eq!(undo_redo_manager.get_stack_size(0), 2);
Ok(())
}
#[test]
fn test_delete_text_no_merge_selection_delete() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello World")?;
document_editing_controller::delete_text(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&DeleteTextDto {
position: 5,
anchor: 11,
},
)?;
let _pos = backspace(&db_context, &event_hub, &mut undo_redo_manager, 5)?;
assert_eq!(undo_redo_manager.get_stack_size(0), 2);
Ok(())
}
#[test]
fn test_delete_text_merge_word_boundary() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("ab cd")?;
let pos = backspace(&db_context, &event_hub, &mut undo_redo_manager, 5)?; let pos = backspace(&db_context, &event_hub, &mut undo_redo_manager, pos)?; let pos = backspace(&db_context, &event_hub, &mut undo_redo_manager, pos)?;
assert_eq!(undo_redo_manager.get_stack_size(0), 1);
let _pos = backspace(&db_context, &event_hub, &mut undo_redo_manager, pos)?;
assert_eq!(export_text(&db_context, &event_hub)?, "a");
assert_eq!(undo_redo_manager.get_stack_size(0), 2);
undo_redo_manager.undo(None)?;
assert_eq!(export_text(&db_context, &event_hub)?, "ab");
undo_redo_manager.undo(None)?;
assert_eq!(export_text(&db_context, &event_hub)?, "ab cd");
Ok(())
}