extern crate text_document_editing as document_editing;
use anyhow::Result;
use test_harness::{
block_controller, export_text, get_block_ids, get_element_ids, inline_element_controller,
setup_with_text,
};
use document_editing::document_editing_controller;
use document_editing::{InsertFragmentDto, InsertHtmlAtPositionDto, InsertMarkdownAtPositionDto};
#[test]
fn test_insert_markdown_simple_paragraph() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
let result = document_editing_controller::insert_markdown_at_position(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertMarkdownAtPositionDto {
position: 5,
anchor: 5,
markdown: "Hello **world**".to_string(),
},
)?;
assert_eq!(result.blocks_added, 0);
let text = export_text(&db_context, &event_hub)?;
assert!(text.contains("world"));
let block_ids = get_block_ids(&db_context)?;
let mut found_bold = false;
for block_id in &block_ids {
let elem_ids = get_element_ids(&db_context, block_id)?;
for elem_id in &elem_ids {
let elem = inline_element_controller::get(&db_context, elem_id)?;
if let Some(elem) = elem
&& let common::format_runs::InlineContent::Text(ref t) = elem.content
&& t == "world"
{
assert_eq!(elem.fmt_font_bold, Some(true));
found_bold = true;
}
}
}
assert!(found_bold, "Should find a bold 'world' element");
Ok(())
}
#[test]
fn test_insert_markdown_multiple_paragraphs() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Start")?;
let result = document_editing_controller::insert_markdown_at_position(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertMarkdownAtPositionDto {
position: 5,
anchor: 5,
markdown: "Para 1\n\nPara 2".to_string(),
},
)?;
assert_eq!(
result.blocks_added, 1,
"Two-block merge should add 1 block (tail)"
);
let text = export_text(&db_context, &event_hub)?;
assert!(text.contains("Para 1"));
assert!(text.contains("Para 2"));
Ok(())
}
#[test]
fn test_insert_markdown_heading() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Text")?;
let _result = document_editing_controller::insert_markdown_at_position(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertMarkdownAtPositionDto {
position: 4,
anchor: 4,
markdown: "# Title".to_string(),
},
)?;
let block_ids = get_block_ids(&db_context)?;
let mut found_heading = false;
for block_id in &block_ids {
let block = block_controller::get(&db_context, block_id)?;
if let Some(block) = block
&& block.fmt_heading_level == Some(1)
{
found_heading = true;
break;
}
}
assert!(found_heading, "Should find a block with heading_level=1");
Ok(())
}
#[test]
fn test_insert_markdown_list() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Text")?;
let _result = document_editing_controller::insert_markdown_at_position(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertMarkdownAtPositionDto {
position: 4,
anchor: 4,
markdown: "- item1\n- item2".to_string(),
},
)?;
let block_ids = get_block_ids(&db_context)?;
let mut list_blocks = 0;
for block_id in &block_ids {
let block = block_controller::get(&db_context, block_id)?;
if let Some(block) = block
&& block.list.is_some()
{
list_blocks += 1;
}
}
assert!(
list_blocks >= 2,
"Should have at least 2 blocks with list associations"
);
Ok(())
}
#[test]
fn test_insert_markdown_undo() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
let original_text = export_text(&db_context, &event_hub)?;
let _result = document_editing_controller::insert_markdown_at_position(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertMarkdownAtPositionDto {
position: 5,
anchor: 5,
markdown: "**bold text**".to_string(),
},
)?;
let after_insert = export_text(&db_context, &event_hub)?;
assert_ne!(after_insert, original_text);
undo_redo_manager.undo(None)?;
let after_undo = export_text(&db_context, &event_hub)?;
assert_eq!(after_undo, original_text);
Ok(())
}
#[test]
fn test_insert_html_simple() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
let result = document_editing_controller::insert_html_at_position(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertHtmlAtPositionDto {
position: 5,
anchor: 5,
html: "<p>Hello <b>world</b></p>".to_string(),
},
)?;
assert_eq!(result.blocks_added, 0);
let text = export_text(&db_context, &event_hub)?;
assert!(text.contains("world"));
let block_ids = get_block_ids(&db_context)?;
let mut found_bold = false;
for block_id in &block_ids {
let elem_ids = get_element_ids(&db_context, block_id)?;
for elem_id in &elem_ids {
let elem = inline_element_controller::get(&db_context, elem_id)?;
if let Some(elem) = elem
&& let common::format_runs::InlineContent::Text(ref t) = elem.content
&& t == "world"
{
assert_eq!(elem.fmt_font_bold, Some(true));
found_bold = true;
}
}
}
assert!(found_bold, "Should find a bold 'world' element");
Ok(())
}
#[test]
fn test_insert_html_multiple_paragraphs() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Start")?;
let result = document_editing_controller::insert_html_at_position(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertHtmlAtPositionDto {
position: 5,
anchor: 5,
html: "<p>A</p><p>B</p>".to_string(),
},
)?;
assert_eq!(
result.blocks_added, 1,
"Two-block merge should add 1 block (tail)"
);
let text = export_text(&db_context, &event_hub)?;
assert!(text.contains("A"));
assert!(text.contains("B"));
Ok(())
}
#[test]
fn test_insert_html_undo() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
let original_text = export_text(&db_context, &event_hub)?;
let _result = document_editing_controller::insert_html_at_position(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertHtmlAtPositionDto {
position: 5,
anchor: 5,
html: "<p><b>bold</b></p>".to_string(),
},
)?;
let after_insert = export_text(&db_context, &event_hub)?;
assert_ne!(after_insert, original_text);
undo_redo_manager.undo(None)?;
let after_undo = export_text(&db_context, &event_hub)?;
assert_eq!(after_undo, original_text);
Ok(())
}
fn make_bold_fragment(text: &str) -> String {
serde_json::json!({
"blocks": [{
"plain_text": text,
"elements": [{
"content": {"Text": text},
"fmt_font_family": null,
"fmt_font_point_size": null,
"fmt_font_weight": null,
"fmt_font_bold": true,
"fmt_font_italic": null,
"fmt_font_underline": null,
"fmt_font_overline": null,
"fmt_font_strikeout": null,
"fmt_letter_spacing": null,
"fmt_word_spacing": null,
"fmt_anchor_href": null,
"fmt_anchor_names": [],
"fmt_is_anchor": null,
"fmt_tooltip": null,
"fmt_underline_style": null,
"fmt_vertical_alignment": null
}],
"heading_level": null,
"list": null,
"alignment": null,
"indent": null,
"text_indent": null,
"marker": null,
"top_margin": null,
"bottom_margin": null,
"left_margin": null,
"right_margin": null,
"tab_positions": []
}]
})
.to_string()
}
#[test]
fn test_insert_fragment_simple() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
let fragment_json = make_bold_fragment("bold text");
let result = document_editing_controller::insert_fragment(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertFragmentDto {
position: 5,
anchor: 5,
fragment_data: fragment_json,
},
)?;
assert_eq!(result.blocks_added, 0);
let block_ids = get_block_ids(&db_context)?;
let mut found_bold = false;
for block_id in &block_ids {
let elem_ids = get_element_ids(&db_context, block_id)?;
for elem_id in &elem_ids {
let elem = inline_element_controller::get(&db_context, elem_id)?.unwrap();
if let common::format_runs::InlineContent::Text(ref t) = elem.content
&& t.contains("bold text")
&& elem.fmt_font_bold == Some(true)
{
found_bold = true;
}
}
}
assert!(found_bold, "Should find a bold 'bold text' element");
Ok(())
}
#[test]
fn test_insert_fragment_undo() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello")?;
let original_text = export_text(&db_context, &event_hub)?;
let fragment_json = make_bold_fragment("bold text");
let _result = document_editing_controller::insert_fragment(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertFragmentDto {
position: 5,
anchor: 5,
fragment_data: fragment_json,
},
)?;
let after_insert = export_text(&db_context, &event_hub)?;
assert_ne!(after_insert, original_text);
undo_redo_manager.undo(None)?;
let after_undo = export_text(&db_context, &event_hub)?;
assert_eq!(after_undo, original_text);
Ok(())
}
#[test]
fn test_insert_markdown_code_block() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Before")?;
let result = document_editing_controller::insert_markdown_at_position(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertMarkdownAtPositionDto {
position: 6,
anchor: 6,
markdown: "```\nfn main() {}\n```".to_string(),
},
)?;
assert!(result.blocks_added >= 1);
let block_ids = get_block_ids(&db_context)?;
let mut found_code = false;
for block_id in &block_ids {
let elem_ids = get_element_ids(&db_context, block_id)?;
for elem_id in &elem_ids {
let elem = inline_element_controller::get(&db_context, elem_id)?.unwrap();
if let common::format_runs::InlineContent::Text(ref t) = elem.content
&& t.contains("fn main")
{
assert_eq!(elem.fmt_font_family, Some("monospace".to_string()));
found_code = true;
}
}
}
assert!(found_code, "Should find a code-formatted element");
Ok(())
}
#[test]
fn test_insert_markdown_nested_bold_italic() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Start")?;
let result = document_editing_controller::insert_markdown_at_position(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertMarkdownAtPositionDto {
position: 5,
anchor: 5,
markdown: "***bold italic***".to_string(),
},
)?;
assert_eq!(result.blocks_added, 0);
let block_ids = get_block_ids(&db_context)?;
let mut found_bi = false;
for block_id in &block_ids {
let elem_ids = get_element_ids(&db_context, block_id)?;
for elem_id in &elem_ids {
let elem = inline_element_controller::get(&db_context, elem_id)?.unwrap();
if elem.fmt_font_bold == Some(true) && elem.fmt_font_italic == Some(true) {
found_bi = true;
}
}
}
assert!(found_bi, "Should find an element with both bold and italic");
Ok(())
}
#[test]
fn test_insert_html_link() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("See")?;
document_editing_controller::insert_html_at_position(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertHtmlAtPositionDto {
position: 3,
anchor: 3,
html: "<p><a href=\"http://example.com\">link</a></p>".to_string(),
},
)?;
let block_ids = get_block_ids(&db_context)?;
let mut found_link = false;
for block_id in &block_ids {
let elem_ids = get_element_ids(&db_context, block_id)?;
for elem_id in &elem_ids {
let elem = inline_element_controller::get(&db_context, elem_id)?.unwrap();
if elem.fmt_anchor_href == Some("http://example.com".to_string()) {
found_link = true;
}
}
}
assert!(found_link, "Should find an element with link href");
Ok(())
}
#[test]
fn test_insert_html_single_paragraph_merges_inline() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello world")?;
let block_ids_before = get_block_ids(&db_context)?;
let block_count_before = block_ids_before.len();
let result = document_editing_controller::insert_html_at_position(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertHtmlAtPositionDto {
position: 5,
anchor: 5,
html: "<p> <b>beautiful</b></p>".to_string(),
},
)?;
assert_eq!(
result.blocks_added, 0,
"Single inline block should not add blocks"
);
let block_ids_after = get_block_ids(&db_context)?;
assert_eq!(
block_ids_after.len(),
block_count_before,
"Block count should remain the same for inline merge"
);
let text = export_text(&db_context, &event_hub)?;
assert!(
text.contains("Hello beautiful world"),
"Text should be merged inline, got: {}",
text
);
let mut found_bold = false;
for block_id in &block_ids_after {
let elem_ids = get_element_ids(&db_context, block_id)?;
for elem_id in &elem_ids {
let elem = inline_element_controller::get(&db_context, elem_id)?;
if let Some(elem) = elem
&& let common::format_runs::InlineContent::Text(ref t) = elem.content
&& t == "beautiful"
{
assert_eq!(elem.fmt_font_bold, Some(true));
found_bold = true;
}
}
}
assert!(found_bold, "Should find a bold 'beautiful' element");
Ok(())
}
#[test]
fn test_insert_html_single_paragraph_merges_inline_undo() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello world")?;
let original_text = export_text(&db_context, &event_hub)?;
document_editing_controller::insert_html_at_position(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertHtmlAtPositionDto {
position: 5,
anchor: 5,
html: "<b>bold</b>".to_string(),
},
)?;
let after_insert = export_text(&db_context, &event_hub)?;
assert_ne!(after_insert, original_text);
undo_redo_manager.undo(None)?;
let after_undo = export_text(&db_context, &event_hub)?;
assert_eq!(after_undo, original_text);
Ok(())
}
#[test]
fn test_insert_markdown_single_paragraph_merges_inline() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("Hello world")?;
let block_ids_before = get_block_ids(&db_context)?;
let block_count_before = block_ids_before.len();
let result = document_editing_controller::insert_markdown_at_position(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertMarkdownAtPositionDto {
position: 6, anchor: 6,
markdown: "**beautiful** ".to_string(),
},
)?;
assert_eq!(
result.blocks_added, 0,
"Single inline block should not add blocks"
);
let block_ids_after = get_block_ids(&db_context)?;
assert_eq!(block_ids_after.len(), block_count_before);
let text = export_text(&db_context, &event_hub)?;
assert!(
text.contains("beautiful"),
"Text should be merged inline, got: {}",
text
);
Ok(())
}
#[test]
fn test_insert_html_multi_block_merges_first_and_last() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("HelloWorld")?;
let block_ids_before = get_block_ids(&db_context)?;
assert_eq!(block_ids_before.len(), 1);
let result = document_editing_controller::insert_html_at_position(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertHtmlAtPositionDto {
position: 5,
anchor: 5,
html: "<p>A</p><p>B</p><p>C</p>".to_string(),
},
)?;
assert_eq!(result.blocks_added, 2);
let text = export_text(&db_context, &event_hub)?;
assert_eq!(
text, "HelloA\nB\nCWorld",
"Expected merged first/last with middle blocks, got: {}",
text
);
let block_ids_after = get_block_ids(&db_context)?;
assert_eq!(block_ids_after.len(), 3, "Should have 3 blocks total");
Ok(())
}
#[test]
fn test_insert_html_two_blocks_no_middle() -> Result<()> {
let (db_context, event_hub, mut undo_redo_manager) = setup_with_text("HelloWorld")?;
let result = document_editing_controller::insert_html_at_position(
&db_context,
&event_hub,
&mut undo_redo_manager,
None,
&InsertHtmlAtPositionDto {
position: 5,
anchor: 5,
html: "<p>A</p><p>B</p>".to_string(),
},
)?;
assert_eq!(result.blocks_added, 1);
let text = export_text(&db_context, &event_hub)?;
assert_eq!(
text, "HelloA\nBWorld",
"Expected two-block merge, got: {}",
text
);
Ok(())
}