loro 1.12.0

Loro is a high-performance CRDTs framework. Make your app collaborative efforlessly.
Documentation
use loro::{CommitOptions, LoroDoc, VersionVector, ID};

#[test]
fn explicit_empty_commit_swallow_options() {
    let doc = LoroDoc::new();
    doc.set_peer_id(1).unwrap();

    // Set options but do an explicit empty commit; options should be swallowed.
    doc.set_next_commit_message("will be swallowed");
    doc.set_next_commit_timestamp(123);
    doc.commit();

    // Next real commit should NOT carry the previous options.
    let text = doc.get_text("text");
    text.insert(0, "x").unwrap();
    doc.commit();

    let change = doc.get_change(ID::new(1, 0)).unwrap();
    assert_eq!(change.message(), "");
    assert_eq!(change.timestamp(), 0);
}

#[test]
fn implicit_empty_commit_preserves_options() {
    let doc = LoroDoc::new();
    doc.set_peer_id(1).unwrap();

    // First real commit just to move the counter
    let text = doc.get_text("text");
    text.insert(0, "123").unwrap();
    doc.commit_with(
        CommitOptions::new()
            .commit_msg("first commit")
            .timestamp(100),
    );

    // Set options and trigger an implicit empty commit via export
    doc.set_next_commit_message("second commit");
    doc.set_next_commit_timestamp(200);
    let _ = doc.export(loro::ExportMode::Snapshot).unwrap();

    // Next real commit should carry the preserved options
    text.insert(3, "456").unwrap();
    doc.commit();

    let first_change = doc.get_change(ID::new(1, 0)).unwrap();
    let second_change = doc.get_change(ID::new(1, 3)).unwrap();
    assert_eq!(first_change.message(), "first commit");
    assert_eq!(first_change.timestamp(), 100);
    assert_eq!(second_change.message(), "second commit");
    assert_eq!(second_change.timestamp(), 200);
}

#[test]
fn test_commit_message() {
    let doc = LoroDoc::new();
    let text = doc.get_text("text");
    text.insert(0, "hello").unwrap();
    doc.commit_with(CommitOptions::new().commit_msg("edits"));
    let change = doc.get_change(ID::new(doc.peer_id(), 0)).unwrap();
    assert_eq!(change.message(), "edits");

    // The commit message can be synced to other peers as well
    let doc2 = LoroDoc::new();
    doc2.import(&doc.export(loro::ExportMode::Snapshot).unwrap())
        .unwrap();
    let change = doc.get_change(ID::new(doc.peer_id(), 1)).unwrap();
    assert_eq!(change.message(), "edits");
}

#[test]
fn changes_with_commit_message_won_t_merge() {
    let doc = LoroDoc::new();
    let text = doc.get_text("text");

    text.insert(0, "hello").unwrap();
    doc.commit_with(CommitOptions::new().commit_msg("edit 1"));

    text.insert(5, " world").unwrap();
    doc.commit_with(CommitOptions::new().commit_msg("edit 2"));

    assert_eq!(text.to_string(), "hello world");

    let change1 = doc.get_change(ID::new(doc.peer_id(), 1)).unwrap();
    let change2 = doc.get_change(ID::new(doc.peer_id(), 6)).unwrap();

    assert_eq!(change1.message(), "edit 1");
    assert_eq!(change2.message(), "edit 2");
}

#[test]
fn test_syncing_commit_message() {
    let doc1 = LoroDoc::new();
    doc1.set_peer_id(1).unwrap();
    let text1 = doc1.get_text("text");

    text1.insert(0, "hello").unwrap();
    doc1.commit_with(CommitOptions::new().commit_msg("edit on doc1"));

    let doc2 = LoroDoc::new();
    doc2.set_peer_id(2).unwrap();

    // Export changes from doc1 and import to doc2
    let changes = doc1.export(loro::ExportMode::all_updates());
    doc2.import(&changes.unwrap()).unwrap();

    // Verify the commit message was synced
    let change = doc2.get_change(ID::new(1, 1)).unwrap();
    assert_eq!(change.message(), "edit on doc1");

    // Verify the text content was also synced
    let text2 = doc2.get_text("text");
    assert_eq!(text2.to_string(), "hello");
}

#[test]
fn test_commit_message_sync_via_snapshot() {
    let doc1 = LoroDoc::new();
    doc1.set_peer_id(1).unwrap();
    let text1 = doc1.get_text("text");

    text1.insert(0, "hello").unwrap();
    doc1.commit_with(CommitOptions::new().commit_msg("first edit"));

    text1.insert(5, " world").unwrap();
    doc1.commit_with(CommitOptions::new().commit_msg("second edit"));

    // Create a snapshot of doc1
    let snapshot = doc1.export(loro::ExportMode::Snapshot);

    // Create a new doc from the snapshot
    let doc2 = LoroDoc::new();
    doc2.import(&snapshot.unwrap()).unwrap();

    // Verify the commit messages were preserved in the snapshot
    let change1 = doc2.get_change(ID::new(1, 1)).unwrap();
    let change2 = doc2.get_change(ID::new(1, 6)).unwrap();

    assert_eq!(change1.message(), "first edit");
    assert_eq!(change2.message(), "second edit");

    // Verify the text content was also preserved
    let text2 = doc2.get_text("text");
    assert_eq!(text2.to_string(), "hello world");
}

#[test]
fn test_commit_message_sync_via_fast_snapshot() {
    let doc1 = LoroDoc::new();
    let doc2 = LoroDoc::new();
    doc1.set_peer_id(1).unwrap();
    let text1 = doc1.get_text("text");

    text1.insert(0, "hello").unwrap();
    doc1.commit_with(CommitOptions::new().commit_msg("first edit"));

    text1.insert(5, " world").unwrap();
    doc1.commit_with(CommitOptions::new().commit_msg("second edit"));

    let snapshot = doc1.export(loro::ExportMode::Snapshot);
    doc2.import(&snapshot.unwrap()).unwrap();

    // Verify the commit messages were preserved in the snapshot
    let change1 = doc2.get_change(ID::new(1, 1)).unwrap();
    let change2 = doc2.get_change(ID::new(1, 6)).unwrap();

    assert_eq!(change1.message(), "first edit");
    assert_eq!(change2.message(), "second edit");

    // Verify the text content was also preserved
    let text2 = doc2.get_text("text");
    assert_eq!(text2.to_string(), "hello world");
    text2.delete(0, 10).unwrap();
    doc2.set_next_commit_message("From text2");
    doc1.import(&doc2.export(loro::ExportMode::Snapshot).unwrap())
        .unwrap();
    let c = doc1.get_change(ID::new(doc2.peer_id(), 0)).unwrap();
    assert_eq!(c.message(), "From text2");
}

#[test]
fn test_commit_message_json_updates() {
    let doc1 = LoroDoc::new();
    let text1 = doc1.get_text("text");

    text1.insert(0, "hello").unwrap();
    doc1.commit_with(CommitOptions::new().commit_msg("first edit"));

    text1.insert(5, " world").unwrap();
    doc1.commit_with(CommitOptions::new().commit_msg("second edit"));

    let start_vv = VersionVector::new();
    let end_vv = doc1.oplog_vv();
    let json_updates = doc1.export_json_updates(&start_vv, &end_vv);

    let doc2 = LoroDoc::new();
    doc2.import_json_updates(json_updates).unwrap();

    // Verify the commit messages were preserved in the JSON updates
    let change1 = doc2.get_change(ID::new(doc1.peer_id(), 1)).unwrap();
    let change2 = doc2.get_change(ID::new(doc1.peer_id(), 6)).unwrap();

    assert_eq!(change1.message(), "first edit");
    assert_eq!(change2.message(), "second edit");

    // Verify the text content was also preserved
    let text2 = doc2.get_text("text");
    assert_eq!(text2.to_string(), "hello world");
}