loro 1.13.9

Loro is a high-performance CRDTs framework. Make your app collaborative efforlessly.
Documentation
use loro::{
    awareness::{Awareness, EphemeralStore},
    cursor::{Cursor, Side},
    LoroDoc, LoroValue, ToJson, ID,
};

const EXPECTED_JSON: &[u8] =
    include_bytes!("../../../loro-js/tests/fixtures/rust/snapshot.deep.json");
const RUST_UPDATES: &[u8] = include_bytes!("../../../loro-js/tests/fixtures/rust/updates.blob");
const TS_UPDATES: &[u8] = include_bytes!("../../../loro-js/tests/fixtures/rust/updates.ts.blob");
const TS_SNAPSHOT: &[u8] = include_bytes!("../../../loro-js/tests/fixtures/rust/snapshot.ts.blob");
const TS_RUNTIME_UPDATES: &[u8] =
    include_bytes!("../../../loro-js/tests/fixtures/rust/runtime-updates.ts.blob");
const TS_RUNTIME_SNAPSHOT: &[u8] =
    include_bytes!("../../../loro-js/tests/fixtures/rust/runtime-snapshot.ts.blob");
const TS_RUNTIME_EXPECTED_JSON: &[u8] =
    include_bytes!("../../../loro-js/tests/fixtures/rust/runtime.expected.json");
const TS_CONCURRENT_BASE: &[u8] =
    include_bytes!("../../../loro-js/tests/fixtures/rust/concurrent-base.ts.blob");
const TS_CONCURRENT_LEFT: &[u8] =
    include_bytes!("../../../loro-js/tests/fixtures/rust/concurrent-left.ts.blob");
const TS_CONCURRENT_RIGHT: &[u8] =
    include_bytes!("../../../loro-js/tests/fixtures/rust/concurrent-right.ts.blob");
const TS_CONCURRENT_EXPECTED_JSON: &[u8] =
    include_bytes!("../../../loro-js/tests/fixtures/rust/concurrent.expected.json");
const TS_FUGUE_LEFT: &[u8] =
    include_bytes!("../../../loro-js/tests/fixtures/rust/fugue-left.ts.blob");
const TS_FUGUE_RIGHT: &[u8] =
    include_bytes!("../../../loro-js/tests/fixtures/rust/fugue-right.ts.blob");
const TS_SHALLOW_SNAPSHOT: &[u8] =
    include_bytes!("../../../loro-js/tests/fixtures/rust/shallow.ts.blob");
const TS_CURSOR: &[u8] = include_bytes!("../../../loro-js/tests/fixtures/rust/cursor.ts.blob");
const TS_AWARENESS: &[u8] =
    include_bytes!("../../../loro-js/tests/fixtures/rust/awareness.ts.blob");
const TS_EPHEMERAL: &[u8] =
    include_bytes!("../../../loro-js/tests/fixtures/rust/ephemeral.ts.blob");

fn expected_json() -> serde_json::Value {
    serde_json::from_slice(EXPECTED_JSON).expect("valid expected JSON fixture")
}

#[test]
fn imports_typescript_reencoded_updates() {
    let expected = LoroDoc::new();
    expected
        .import(RUST_UPDATES)
        .expect("valid source FastUpdates fixture");
    let doc = LoroDoc::new();
    doc.import(TS_UPDATES)
        .expect("Rust should import TypeScript-encoded FastUpdates");
    assert_eq!(
        doc.get_deep_value().to_json_value(),
        expected.get_deep_value().to_json_value()
    );
}

#[test]
fn imports_typescript_reencoded_snapshot() {
    let doc = LoroDoc::new();
    doc.import(TS_SNAPSHOT)
        .expect("Rust should import TypeScript-encoded FastSnapshot");
    assert_eq!(doc.get_deep_value().to_json_value(), expected_json());
}

#[test]
fn imports_typescript_runtime_updates() {
    let doc = LoroDoc::new();
    doc.import(TS_RUNTIME_UPDATES)
        .expect("Rust should import updates produced by the TypeScript runtime");
    let expected: serde_json::Value =
        serde_json::from_slice(TS_RUNTIME_EXPECTED_JSON).expect("valid runtime JSON fixture");
    assert_eq!(doc.get_deep_value().to_json_value(), expected);
}

#[test]
fn imports_typescript_runtime_snapshot() {
    let doc = LoroDoc::new();
    doc.import(TS_RUNTIME_SNAPSHOT)
        .expect("Rust should import a snapshot produced by the TypeScript runtime");
    let updates_doc = LoroDoc::new();
    updates_doc
        .import(TS_RUNTIME_UPDATES)
        .expect("Rust should import matching TypeScript updates");
    let expected: serde_json::Value =
        serde_json::from_slice(TS_RUNTIME_EXPECTED_JSON).expect("valid runtime JSON fixture");
    assert_eq!(doc.get_deep_value().to_json_value(), expected);
    assert_eq!(
        doc.get_text("text").to_delta(),
        updates_doc.get_text("text").to_delta()
    );
    assert_eq!(
        doc.get_text("text").get_richtext_value().to_json_value(),
        serde_json::json!([{ "insert": "b", "attributes": { "bold": true } }])
    );
}

#[test]
fn typescript_concurrent_updates_match_rust_in_both_import_orders() {
    let expected: serde_json::Value =
        serde_json::from_slice(TS_CONCURRENT_EXPECTED_JSON).expect("valid concurrent JSON fixture");
    let left_first = LoroDoc::new();
    left_first.import(TS_CONCURRENT_BASE).unwrap();
    left_first.import(TS_CONCURRENT_LEFT).unwrap();
    left_first.import(TS_CONCURRENT_RIGHT).unwrap();
    let right_first = LoroDoc::new();
    right_first.import(TS_CONCURRENT_BASE).unwrap();
    right_first.import(TS_CONCURRENT_RIGHT).unwrap();
    right_first.import(TS_CONCURRENT_LEFT).unwrap();

    assert_eq!(left_first.get_deep_value().to_json_value(), expected);
    assert_eq!(right_first.get_deep_value().to_json_value(), expected);
}

#[test]
fn typescript_fugue_updates_match_rust_in_both_import_orders() {
    let expected = serde_json::json!({ "text": "Hello World!" });
    let left_first = LoroDoc::new();
    left_first.import(TS_FUGUE_LEFT).unwrap();
    left_first.import(TS_FUGUE_RIGHT).unwrap();
    let right_first = LoroDoc::new();
    right_first.import(TS_FUGUE_RIGHT).unwrap();
    right_first.import(TS_FUGUE_LEFT).unwrap();

    assert_eq!(left_first.get_deep_value().to_json_value(), expected);
    assert_eq!(right_first.get_deep_value().to_json_value(), expected);
}

#[test]
fn imports_typescript_shallow_snapshot_and_checks_out_its_root() {
    let doc = LoroDoc::new();
    doc.import(TS_SHALLOW_SNAPSHOT)
        .expect("Rust should import a TypeScript shallow snapshot");

    assert!(doc.is_shallow());
    assert_eq!(doc.shallow_since_vv().get(&77).copied(), Some(4));
    assert_eq!(
        doc.get_deep_value().to_json_value(),
        serde_json::json!({ "text": "0123456789" })
    );

    let root = doc.shallow_since_frontiers();
    doc.checkout(&root)
        .expect("the retained shallow root should be checkout-able");
    assert_eq!(
        doc.get_deep_value().to_json_value(),
        serde_json::json!({ "text": "01234" })
    );
}

#[test]
fn decodes_typescript_cursor_with_rust() {
    let cursor = Cursor::decode(TS_CURSOR).expect("Rust should decode a TypeScript cursor");
    assert_eq!(cursor.id, Some(ID::new(99, 1)));
    assert_eq!(cursor.container.to_string(), "cid:root-text:Text");
    assert_eq!(cursor.side, Side::Middle);
}

#[allow(deprecated)]
#[test]
fn imports_typescript_awareness_with_rust() {
    let mut awareness = Awareness::new(456, 30_000);
    let (updated, added) = awareness
        .try_apply(TS_AWARENESS)
        .expect("Rust should decode TypeScript awareness data");
    assert!(updated.is_empty());
    assert_eq!(added, vec![123]);
    let state = &awareness.get_all_states().get(&123).unwrap().state;
    assert_eq!(
        state.to_json_value(),
        serde_json::json!({ "status": "typing", "position": 3 })
    );
}

#[test]
fn imports_typescript_ephemeral_state_with_rust() {
    let store = EphemeralStore::new(i64::MAX);
    store
        .apply(TS_EPHEMERAL)
        .expect("Rust should decode TypeScript ephemeral data");
    assert_eq!(store.get("cursor"), Some(LoroValue::from(7)));
}