//! Tests for [`super::engine`] (the `EditorEngine` state machine).
//!
//! Moved out of `engine.rs` to keep the production file focused on the
//! state machine itself. The tests still exercise the same surface with
//! `use super::engine::*;` so no visibility boundaries are crossed.

use super::engine::*;
use crate::command::command::Command;
use crate::error::EditorError;
use crate::id::SequentialIdGenerator;
use crate::model::block::{Block, BlockId};
use crate::model::document::Document;
use crate::model::inline::TextSpan;
use crate::model::registry::{BlockTypeDef, MarkTypeDef};
use crate::model::selection::{Position, Selection};
use serde_json::{Map, Value};

fn test_engine() -> EditorEngine {
    let mut engine = EditorEngine::new(Box::new(SequentialIdGenerator::new("block")));
    engine.register_mark_type(MarkTypeDef::new("color").with_tag("span"));
    engine.register_mark_type(MarkTypeDef::new("highlight").with_tag("mark"));
    engine
}

fn engine_with_text(text: &str) -> EditorEngine {
    let mut engine = test_engine();
    if !text.is_empty() {
        engine
            .execute(Command::InsertText {
                text: text.to_string(),
            })
            .unwrap();
    }
    engine
}

fn engine_with_three_blocks() -> EditorEngine {
    let mut engine = test_engine();
    engine
        .execute_batch(vec![
            Command::InsertText { text: "one".into() },
            Command::Enter,
            Command::InsertText { text: "two".into() },
            Command::Enter,
            Command::InsertText {
                text: "three".into(),
            },
        ])
        .unwrap();
    engine
}

#[test]
fn insert_text() {
    let mut engine = test_engine();
    engine
        .execute(Command::InsertText {
            text: "hello".into(),
        })
        .unwrap();

    assert_eq!(
        engine.document().plain_text(&BlockId::new("initial")),
        "hello"
    );
}

#[test]
fn undo_insert() {
    let mut engine = test_engine();
    engine
        .execute(Command::InsertText {
            text: "hello".into(),
        })
        .unwrap();

    engine.undo().unwrap();
    assert_eq!(engine.document().plain_text(&BlockId::new("initial")), "");
}

#[test]
fn redo_after_undo() {
    let mut engine = test_engine();
    engine
        .execute(Command::InsertText {
            text: "hello".into(),
        })
        .unwrap();
    engine.undo().unwrap();
    engine.redo().unwrap();
    assert_eq!(
        engine.document().plain_text(&BlockId::new("initial")),
        "hello"
    );
}

#[test]
fn enter_splits_and_undo_merges() {
    let mut engine = engine_with_text("hello world");
    engine.set_selection(Some(Selection::collapsed("initial", 5)));
    engine.execute(Command::Enter).unwrap();

    assert_eq!(engine.document().root_block_count(), 2);

    engine.undo().unwrap();
    assert_eq!(engine.document().root_block_count(), 1);
    assert_eq!(
        engine.document().plain_text(&BlockId::new("initial")),
        "hello world"
    );
}

#[test]
fn backspace_and_undo() {
    let mut engine = engine_with_text("hello");
    engine.execute(Command::Backspace).unwrap();
    assert_eq!(
        engine.document().plain_text(&BlockId::new("initial")),
        "hell"
    );

    engine.undo().unwrap();
    assert_eq!(
        engine.document().plain_text(&BlockId::new("initial")),
        "hello"
    );
}

#[test]
fn cross_block_backspace_undo_redo_restores_deleted_blocks() {
    let mut engine = engine_with_three_blocks();
    let root = engine.document().root_block_ids().to_vec();
    engine.set_selection(Some(Selection::new(
        Position::new(root[0].clone(), 1),
        Position::new(root[2].clone(), 2),
    )));

    engine.execute(Command::Backspace).unwrap();

    assert_eq!(engine.document().root_block_ids(), [root[0].clone()]);
    assert_eq!(engine.document().plain_text(&root[0]), "oree");
    assert_eq!(
        engine.selection(),
        &Some(Selection::collapsed(root[0].clone(), 1))
    );

    assert!(engine.undo().unwrap());
    assert_eq!(engine.document().root_block_ids(), root.as_slice());
    assert_eq!(engine.document().plain_text(&root[0]), "one");
    assert_eq!(engine.document().plain_text(&root[1]), "two");
    assert_eq!(engine.document().plain_text(&root[2]), "three");

    assert!(engine.redo().unwrap());
    assert_eq!(engine.document().root_block_ids(), [root[0].clone()]);
    assert_eq!(engine.document().plain_text(&root[0]), "oree");
    assert_eq!(
        engine.selection(),
        &Some(Selection::collapsed(root[0].clone(), 1))
    );
}

#[test]
fn cross_block_backspace_undo_recreates_different_end_block_type_before_restoring_it() {
    let mut doc = Document::new();
    let mut a = Block::new_paragraph(BlockId::new("a"));
    a.content = vec![TextSpan::plain("Alpha")];
    let mut b = Block::new_paragraph(BlockId::new("b"));
    b.content = vec![TextSpan::plain("Bravo")];
    let mut c = Block::new_heading(BlockId::new("c"), 2);
    c.content = vec![TextSpan::plain("Charlie")];
    doc.insert_block(a, 0);
    doc.insert_block(b, 1);
    doc.insert_block(c, 2);

    let mut engine = EditorEngine::from_document(doc, Box::new(SequentialIdGenerator::new("t")));
    engine.set_selection(Some(Selection::new(
        Position::new("a", 2),
        Position::new("c", 3),
    )));

    engine.execute(Command::Backspace).unwrap();
    assert_eq!(engine.document().root_block_ids(), &[BlockId::new("a")]);
    assert_eq!(engine.document().plain_text(&BlockId::new("a")), "Alrlie");

    assert!(engine.undo().unwrap());
    assert_eq!(
        engine.document().root_block_ids(),
        &[BlockId::new("a"), BlockId::new("b"), BlockId::new("c")]
    );
    assert!(
        engine
            .document()
            .get_block(&BlockId::new("c"))
            .unwrap()
            .is_heading()
    );
    assert_eq!(engine.document().plain_text(&BlockId::new("a")), "Alpha");
    assert_eq!(engine.document().plain_text(&BlockId::new("b")), "Bravo");
    assert_eq!(engine.document().plain_text(&BlockId::new("c")), "Charlie");
}

#[test]
fn set_block_type_and_undo() {
    let mut engine = engine_with_text("Title");
    let mut props = Map::new();
    props.insert("level".into(), Value::Number(1.into()));

    engine
        .execute(Command::SetBlockType {
            block_type: "heading".into(),
            props,
        })
        .unwrap();

    let block = engine
        .document()
        .get_block(&BlockId::new("initial"))
        .unwrap();
    assert!(block.is_heading());

    engine.undo().unwrap();
    let block = engine
        .document()
        .get_block(&BlockId::new("initial"))
        .unwrap();
    assert!(block.is_paragraph());
}

#[test]
fn batch_dispatches_as_single_undo() {
    let mut engine = test_engine();

    engine
        .execute_batch(vec![
            Command::InsertText { text: "a".into() },
            Command::InsertText { text: "b".into() },
            Command::Backspace,
        ])
        .unwrap();

    assert_eq!(engine.document().plain_text(&BlockId::new("initial")), "a");
    assert!(engine.can_undo());

    assert!(engine.undo().unwrap());
    assert_eq!(engine.document().plain_text(&BlockId::new("initial")), "");
    assert!(!engine.can_undo());
}

#[test]
fn batch_with_error_rolls_back() {
    let mut engine = test_engine();

    let result = engine.execute_batch(vec![
        Command::InsertText { text: "a".into() },
        Command::ToggleMark {
            mark_type: "unknown".into(),
        },
    ]);

    assert!(matches!(result, Err(EditorError::UnknownMarkType(mark)) if mark == "unknown"));
    assert_eq!(engine.document().plain_text(&BlockId::new("initial")), "");
    assert!(!engine.can_undo());
}

#[test]
fn remove_single_block() {
    let mut engine = engine_with_three_blocks();
    let root = engine.document().root_block_ids().to_vec();

    engine.remove_blocks(&[root[1].clone()]).unwrap();

    assert_eq!(engine.document().root_block_count(), 2);
    assert_eq!(
        engine.document().root_block_ids(),
        [root[0].clone(), root[2].clone()]
    );
    assert_eq!(
        engine.selection(),
        &Some(Selection::collapsed(root[0].clone(), 0))
    );
}

#[test]
fn remove_blocks_and_undo() {
    let mut engine = engine_with_three_blocks();
    let root = engine.document().root_block_ids().to_vec();

    engine
        .remove_blocks(&[root[0].clone(), root[1].clone()])
        .unwrap();

    assert_eq!(engine.document().root_block_ids(), [root[2].clone()]);
    assert_eq!(
        engine.selection(),
        &Some(Selection::collapsed(root[2].clone(), 0))
    );

    assert!(engine.undo().unwrap());
    assert_eq!(engine.document().root_block_ids(), root.as_slice());
    assert_eq!(engine.document().plain_text(&root[0]), "one");
    assert_eq!(engine.document().plain_text(&root[1]), "two");
    assert_eq!(engine.document().plain_text(&root[2]), "three");

    assert!(engine.redo().unwrap());
    assert_eq!(engine.document().root_block_ids(), [root[2].clone()]);
}

#[test]
fn move_blocks_as_single_transaction_preserves_order_and_undo() {
    let mut engine = engine_with_three_blocks();
    let root = engine.document().root_block_ids().to_vec();

    engine
        .move_blocks(&[root[0].clone(), root[1].clone()], 1)
        .unwrap();

    assert_eq!(
        engine.document().root_block_ids(),
        [root[2].clone(), root[0].clone(), root[1].clone()]
    );

    assert!(engine.undo().unwrap());
    assert_eq!(engine.document().root_block_ids(), root.as_slice());

    assert!(engine.redo().unwrap());
    assert_eq!(
        engine.document().root_block_ids(),
        [root[2].clone(), root[0].clone(), root[1].clone()]
    );
}

#[test]
fn move_blocks_upward_preserves_relative_order() {
    let mut engine = engine_with_three_blocks();
    let root = engine.document().root_block_ids().to_vec();

    engine
        .move_blocks(&[root[1].clone(), root[2].clone()], 0)
        .unwrap();

    assert_eq!(
        engine.document().root_block_ids(),
        [root[1].clone(), root[2].clone(), root[0].clone()]
    );
}

#[test]
fn remove_all_blocks_inserts_empty() {
    let mut engine = engine_with_three_blocks();
    let root = engine.document().root_block_ids().to_vec();

    engine.remove_blocks(&root).unwrap();

    let remaining = engine.document().root_block_ids().to_vec();
    assert_eq!(remaining.len(), 1);
    let block = engine.document().get_block(&remaining[0]).unwrap();
    assert!(block.is_paragraph());
    assert_eq!(block.plain_text(), "");
    assert_eq!(
        engine.selection(),
        &Some(Selection::collapsed(remaining[0].clone(), 0))
    );
}

#[test]
fn toggle_bold_on_range() {
    let mut engine = engine_with_text("hello world");
    engine.set_selection(Some(Selection::new(
        Position::new("initial", 6),
        Position::new("initial", 11),
    )));

    engine
        .execute(Command::ToggleMark {
            mark_type: "bold".into(),
        })
        .unwrap();

    let block = engine
        .document()
        .get_block(&BlockId::new("initial"))
        .unwrap();
    assert_eq!(block.content.len(), 2);
    assert!(!block.content[0].has_mark("bold"));
    assert!(block.content[1].has_mark("bold"));
}

#[test]
fn serialization_roundtrip() {
    let engine = engine_with_text("hello world");
    let json = engine.to_json().unwrap();

    let engine2 =
        EditorEngine::from_json(&json, Box::new(SequentialIdGenerator::new("new"))).unwrap();
    assert_eq!(
        engine2.document().plain_text(&BlockId::new("initial")),
        "hello world"
    );
}

#[test]
fn load_json_with_heading_and_marks() {
    let json = r#"{
        "root": ["b1", "b2"],
        "blocks": {
            "b1": {
                "id": "b1", "parent": null, "children": [], "type": "heading",
                "props": {"level": 1},
                "content": [{"text": "Title", "marks": []}]
            },
            "b2": {
                "id": "b2", "parent": null, "children": [], "type": "paragraph",
                "content": [
                    {"text": "normal ", "marks": []},
                    {"text": "bold", "marks": [{"type": "bold"}]}
                ]
            }
        }
    }"#;
    let engine = EditorEngine::from_json(json, Box::new(SequentialIdGenerator::new("t"))).unwrap();
    assert_eq!(engine.document().plain_text(&BlockId::new("b1")), "Title");
    assert_eq!(
        engine.document().plain_text(&BlockId::new("b2")),
        "normal bold"
    );

    // Verify get_block returns content
    let block = engine.document().get_block(&BlockId::new("b1")).unwrap();
    assert_eq!(block.content.len(), 1);
    assert_eq!(block.content[0].text, "Title");

    // Serialize and verify content field is present
    let block_json = serde_json::to_string(block).unwrap();
    assert!(block_json.contains("\"content\""));
    assert!(block_json.contains("Title"));
    println!("Block JSON: {block_json}");
}

#[test]
fn execute_rebuilds_tree_metadata_after_indent() {
    let mut engine = test_engine();

    engine
        .execute(Command::InsertText {
            text: "parent".into(),
        })
        .unwrap();
    engine.execute(Command::Enter).unwrap();
    engine
        .execute(Command::InsertText {
            text: "child".into(),
        })
        .unwrap();
    engine.execute(Command::IndentBlock).unwrap();

    let parent = engine
        .document()
        .get_block(&BlockId::new("initial"))
        .unwrap();
    let child = engine
        .document()
        .get_block(&BlockId::new("block-1"))
        .unwrap();

    assert_eq!(parent.children, vec![BlockId::new("block-1")]);
    assert_eq!(child.parent, Some(BlockId::new("initial")));
}

#[test]
fn execute_batch_serializes_parent_and_children_metadata() {
    let mut engine = test_engine();

    engine
        .execute_batch(vec![
            Command::InsertText {
                text: "parent".into(),
            },
            Command::Enter,
            Command::InsertText {
                text: "child".into(),
            },
            Command::IndentBlock,
        ])
        .unwrap();

    let json = engine.to_json().unwrap();
    let value: Value = serde_json::from_str(&json).unwrap();
    let blocks = value.get("blocks").and_then(Value::as_object).unwrap();
    let parent = blocks.get("initial").and_then(Value::as_object).unwrap();
    let child = blocks.get("block-1").and_then(Value::as_object).unwrap();

    assert_eq!(
        parent.get("children").and_then(Value::as_array).unwrap(),
        &vec![Value::String("block-1".into())]
    );
    assert_eq!(child.get("parent").and_then(Value::as_str), Some("initial"));
}

#[test]
fn multiple_undo_redo() {
    let mut engine = test_engine();
    engine
        .execute(Command::InsertText { text: "a".into() })
        .unwrap();
    engine
        .execute(Command::InsertText { text: "b".into() })
        .unwrap();
    engine
        .execute(Command::InsertText { text: "c".into() })
        .unwrap();

    assert_eq!(
        engine.document().plain_text(&BlockId::new("initial")),
        "abc"
    );

    engine.undo().unwrap();
    assert_eq!(engine.document().plain_text(&BlockId::new("initial")), "ab");

    engine.undo().unwrap();
    assert_eq!(engine.document().plain_text(&BlockId::new("initial")), "a");

    engine.redo().unwrap();
    assert_eq!(engine.document().plain_text(&BlockId::new("initial")), "ab");

    engine.redo().unwrap();
    assert_eq!(
        engine.document().plain_text(&BlockId::new("initial")),
        "abc"
    );
}

#[test]
fn unknown_block_type_rejected() {
    let mut engine = engine_with_text("hello");
    let result = engine.execute(Command::SetBlockType {
        block_type: "nonexistent".into(),
        props: Map::new(),
    });
    assert!(result.is_err());
    match result.unwrap_err() {
        EditorError::UnknownBlockType(t) => assert_eq!(t, "nonexistent"),
        e => panic!("Expected UnknownBlockType, got: {:?}", e),
    }
}

#[test]
fn unknown_mark_type_rejected() {
    let mut engine = engine_with_text("hello world");
    engine.set_selection(Some(Selection::new(
        Position::new("initial", 0),
        Position::new("initial", 5),
    )));

    let result = engine.execute(Command::ToggleMark {
        mark_type: "rainbow".into(),
    });
    assert!(result.is_err());
    match result.unwrap_err() {
        EditorError::UnknownMarkType(t) => assert_eq!(t, "rainbow"),
        e => panic!("Expected UnknownMarkType, got: {:?}", e),
    }
}

#[test]
fn custom_block_type_after_registration() {
    let mut engine = engine_with_text("note content");
    engine.register_block_type(BlockTypeDef::new("callout").with_tag("aside"));

    let mut props = Map::new();
    props.insert("variant".into(), Value::String("info".into()));

    engine
        .execute(Command::SetBlockType {
            block_type: "callout".into(),
            props,
        })
        .unwrap();

    let block = engine
        .document()
        .get_block(&BlockId::new("initial"))
        .unwrap();
    assert!(block.is_type("callout"));
    assert_eq!(block.prop_str("variant"), Some("info"));

    engine.undo().unwrap();
    let block = engine
        .document()
        .get_block(&BlockId::new("initial"))
        .unwrap();
    assert!(block.is_paragraph());
}

#[test]
fn custom_mark_type_after_registration() {
    let mut engine = engine_with_text("hello world");
    engine.register_mark_type(MarkTypeDef::new("highlight").with_tag("mark"));

    engine.set_selection(Some(Selection::new(
        Position::new("initial", 0),
        Position::new("initial", 5),
    )));

    engine
        .execute(Command::ToggleMark {
            mark_type: "highlight".into(),
        })
        .unwrap();

    let block = engine
        .document()
        .get_block(&BlockId::new("initial"))
        .unwrap();
    assert!(block.content[0].has_mark("highlight"));
}

#[test]
fn set_block_align_end_to_end() {
    use crate::model::block::BlockAlignment;

    let mut engine = engine_with_text("hello");

    engine
        .execute(Command::SetBlockAlign {
            align: BlockAlignment::Center,
        })
        .unwrap();

    let block = engine
        .document()
        .get_block(&BlockId::new("initial"))
        .unwrap();
    assert_eq!(block.alignment(), BlockAlignment::Center);

    // Undo restores default (Left).
    engine.undo().unwrap();
    let block = engine
        .document()
        .get_block(&BlockId::new("initial"))
        .unwrap();
    assert_eq!(block.alignment(), BlockAlignment::Left);

    // Redo re-applies alignment.
    engine.redo().unwrap();
    let block = engine
        .document()
        .get_block(&BlockId::new("initial"))
        .unwrap();
    assert_eq!(block.alignment(), BlockAlignment::Center);
}

#[test]
fn set_block_align_across_multiple_blocks_single_transaction() {
    use crate::model::block::BlockAlignment;

    let mut engine = test_engine();
    engine
        .execute_batch(vec![
            Command::InsertText { text: "one".into() },
            Command::Enter,
            Command::InsertText { text: "two".into() },
            Command::Enter,
            Command::InsertText {
                text: "three".into(),
            },
        ])
        .unwrap();

    // Select from start of first block to end of last block.
    let root = engine.document().root_block_ids().to_vec();
    assert_eq!(root.len(), 3);
    engine.set_selection(Some(Selection::new(
        Position::new(root[0].0.as_str(), 0),
        Position::new(root[2].0.as_str(), 5),
    )));

    engine
        .execute(Command::SetBlockAlign {
            align: BlockAlignment::Right,
        })
        .unwrap();

    for id in &root {
        assert_eq!(
            engine.document().get_block(id).unwrap().alignment(),
            BlockAlignment::Right,
            "block {} should be right-aligned",
            id.0
        );
    }

    // One transaction → one undo restores all three.
    engine.undo().unwrap();
    for id in &root {
        assert_eq!(
            engine.document().get_block(id).unwrap().alignment(),
            BlockAlignment::Left
        );
    }
}

#[test]
fn stored_marks_batched_with_insert_text_clears_after() {
    // Reproduces the IME flow: a queued ToggleMark followed by the
    // InsertText emitted at compositionend, dispatched as one batch.
    let mut engine = test_engine();
    engine.set_selection(Some(Selection::collapsed("initial", 0)));

    engine
        .execute_batch(vec![
            Command::ToggleMark {
                mark_type: "bold".into(),
            },
            Command::InsertText { text: "ㅋ".into() },
        ])
        .unwrap();

    let block = engine
        .document()
        .get_block(&BlockId::new("initial"))
        .unwrap();
    assert_eq!(block.plain_text(), "ㅋ");
    assert!(block.content.iter().all(|span| span.has_mark("bold")));
    // After consumption by InsertText, stored_marks should be None so
    // the *next* InsertText (e.g. the next IME composition commit)
    // inherits marks from the document via `active_marks_at`.
    assert!(
        engine.stored_marks().is_none(),
        "expected stored_marks to be None after batch consumption, got {:?}",
        engine.stored_marks()
    );
}

#[test]
fn set_mark_sets_collapsed_stored_marks_with_attrs() {
    let mut engine = test_engine();
    engine.set_selection(Some(Selection::collapsed("initial", 0)));

    let mut style = Map::new();
    style.insert("color".into(), Value::String("#ff0000".into()));
    let mut attrs = Map::new();
    attrs.insert("style".into(), Value::Object(style));

    engine
        .execute(Command::SetMark {
            mark_type: "color".into(),
            attrs,
        })
        .unwrap();

    let stored_marks = engine.stored_marks().as_ref().expect("stored marks set");
    assert_eq!(stored_marks.len(), 1);
    assert_eq!(stored_marks[0].ty, "color");
    assert_eq!(
        stored_marks[0]
            .style()
            .and_then(|s| s.get("color"))
            .and_then(Value::as_str),
        Some("#ff0000")
    );
}

#[test]
fn clear_mark_removes_collapsed_stored_mark() {
    let mut engine = test_engine();
    engine.set_selection(Some(Selection::collapsed("initial", 0)));

    let mut style = Map::new();
    style.insert("color".into(), Value::String("#ff0000".into()));
    let mut attrs = Map::new();
    attrs.insert("style".into(), Value::Object(style));

    engine
        .execute(Command::SetMark {
            mark_type: "color".into(),
            attrs,
        })
        .unwrap();
    engine
        .execute(Command::ClearMark {
            mark_type: "color".into(),
        })
        .unwrap();

    let stored_marks = engine.stored_marks().as_ref().expect("stored marks set");
    assert!(stored_marks.iter().all(|m| m.ty != "color"));
}

#[test]
fn changing_selection_clears_stored_marks() {
    let mut engine = test_engine();
    engine.set_selection(Some(Selection::collapsed("initial", 0)));

    engine
        .execute(Command::ToggleMark {
            mark_type: "bold".into(),
        })
        .unwrap();
    assert!(engine.stored_marks().is_some());

    engine.set_selection(Some(Selection::collapsed("initial", 1)));
    assert!(engine.stored_marks().is_none());
}

#[test]
fn setting_same_selection_preserves_stored_marks() {
    let mut engine = test_engine();
    let selection = Selection::collapsed("initial", 0);
    engine.set_selection(Some(selection.clone()));

    engine
        .execute(Command::ToggleMark {
            mark_type: "bold".into(),
        })
        .unwrap();
    assert!(engine.stored_marks().is_some());

    engine.set_selection(Some(selection));
    assert!(engine.stored_marks().is_some());
}

#[test]
fn toggle_mark_across_blocks_applies_to_each_selected_segment() {
    let mut engine = EditorEngine::from_json(
        r#"{"root":["a","b"],"blocks":{"a":{"id":"a","parent":null,"children":[],"type":"paragraph","content":[{"text":"hello","marks":[]}]},"b":{"id":"b","parent":null,"children":[],"type":"paragraph","content":[{"text":"world","marks":[]}]}}}"#,
        Box::new(SequentialIdGenerator::new("block")),
    )
    .unwrap();
    engine.register_mark_type(MarkTypeDef::new("color").with_tag("span"));
    engine.register_mark_type(MarkTypeDef::new("highlight").with_tag("mark"));

    engine.set_selection(Some(Selection::new(
        Position::new("a", 2),
        Position::new("b", 3),
    )));
    engine
        .execute(Command::ToggleMark {
            mark_type: "bold".into(),
        })
        .unwrap();

    let a = engine.document().get_block(&BlockId::new("a")).unwrap();
    let b = engine.document().get_block(&BlockId::new("b")).unwrap();
    assert!(a.content.iter().any(|span| span.has_mark("bold")));
    assert!(b.content.iter().any(|span| span.has_mark("bold")));
}

#[test]
fn stored_marks_apply_to_next_insert_text() {
    let mut engine = test_engine();
    engine.set_selection(Some(Selection::collapsed("initial", 0)));

    engine
        .execute(Command::ToggleMark {
            mark_type: "bold".into(),
        })
        .unwrap();

    let stored_marks = engine.stored_marks().as_ref().expect("stored marks set");
    assert_eq!(stored_marks.len(), 1);
    assert_eq!(stored_marks[0].ty, "bold");

    engine
        .execute(Command::InsertText {
            text: "hello".into(),
        })
        .unwrap();

    let block = engine
        .document()
        .get_block(&BlockId::new("initial"))
        .unwrap();
    assert_eq!(block.plain_text(), "hello");
    assert!(block.content.iter().all(|span| span.has_mark("bold")));
}
