jmbl 0.6.0

A high performance CRDT
Documentation
use std::time::Duration;

use jmbl::{OpOutput, Value, JMBL};
use litl::Val;

#[test]
fn root_can_be_list_and_insert_works() {
    let mut jmbl = JMBL::new_from_root(
        |view| view.create_list(["hello", "world"]),
        OpOutput::new_dummy(),
    );

    std::thread::sleep(Duration::from_millis(10));

    let jmbl1 = jmbl.readable.clone();

    assert_eq!(
        jmbl1.get_root().if_list().unwrap().at(0),
        Value::str("hello")
    );
    assert_eq!(
        jmbl1.get_root().if_list().unwrap().at(1),
        Value::str("world")
    );
    assert_eq!(
        jmbl1.get_root().if_list().unwrap().at(2),
        Value::plain(Val::null())
    );

    assert_eq!(
        jmbl1.get_root().if_list().unwrap().val(),
        vec![Value::str("hello"), Value::str("world")]
    );

    assert_eq!(
        jmbl1.get_root().if_list().unwrap().val_to_litl(),
        Val::array([Val::str("hello"), Val::str("world")])
    );

    jmbl.change(|root, _| root.if_list_mut().unwrap().insert_at(1, "cruel"));

    let jmbl2 = jmbl.readable.clone();

    assert_eq!(
        jmbl2.get_root().if_list().unwrap().val(),
        vec![
            Value::str("hello"),
            Value::str("cruel"),
            Value::str("world")
        ]
    );
}

#[test]
fn delete_works() {
    let mut jmbl = JMBL::new_from_root(
        |view| view.create_list(["hello", "cruel", "world"]),
        OpOutput::new_dummy(),
    );

    jmbl.change(|root, _| root.if_list_mut().unwrap().delete_at(1));

    let jmbl2 = jmbl.readable.clone();

    assert_eq!(
        jmbl2.get_root().if_list().unwrap().val(),
        vec![Value::str("hello"), Value::str("world")]
    );
}

#[test]
fn push_works() {
    let mut jmbl = JMBL::new_from_root(
        |view| view.create_list(["hello", "world"]),
        OpOutput::new_dummy(),
    );

    jmbl.change(|root, _| root.if_list_mut().unwrap().push("beings"));

    let jmbl2 = jmbl.readable.clone();

    assert_eq!(
        jmbl2.get_root().if_list().unwrap().val(),
        vec![
            Value::str("hello"),
            Value::str("world"),
            Value::str("beings")
        ]
    );
}

#[test]
fn pop_works() {
    let mut jmbl = JMBL::new_from_root(
        |view| view.create_list(["hello", "cruel", "world"]),
        OpOutput::new_dummy(),
    );

    jmbl.change(|root, _| assert_eq!(root.if_list_mut().unwrap().pop(), Some(Value::str("world"))));

    let jmbl2 = jmbl.readable.clone();

    assert_eq!(
        jmbl2.get_root().if_list().unwrap().val(),
        vec![Value::str("hello"), Value::str("cruel")]
    );
}

#[test]
fn concurrent_inserts_are_ordered_correctly() {
    let mut jmbl = JMBL::new_from_root(
        |view| view.create_list(["hello", "world"]),
        OpOutput::new_dummy(),
    );

    let jmbl1 = jmbl.readable.clone();

    let mut jmbl2a = jmbl.switch_to_new_op_output(OpOutput::new_dummy());
    let mut jmbl2b = jmbl.switch_to_new_op_output(OpOutput::new_dummy());
    let mut jmbl2c = jmbl.switch_to_new_op_output(OpOutput::new_dummy());

    jmbl2a.change(|root, _| root.if_list_mut().unwrap().insert_at(1, "bright"));

    std::thread::sleep(Duration::from_millis(10));

    jmbl2b.change(|root, _| root.if_list_mut().unwrap().insert_at(1, "dark"));

    std::thread::sleep(Duration::from_millis(10));

    jmbl2c.change(|root, _| root.if_list_mut().unwrap().insert_at(1, "cheesy"));

    let ops_2b_since_1 = jmbl2b.ops_since(&jmbl1).collect::<Vec<_>>();
    assert_eq!(ops_2b_since_1, jmbl2b.test_get_past_sent_ops().unwrap());

    let ops_2c_since_1 = jmbl2c.ops_since(&jmbl1).collect::<Vec<_>>();
    assert_eq!(ops_2c_since_1, jmbl2c.test_get_past_sent_ops().unwrap());

    let jmbl3 = jmbl2a
        .with_ops_applied(&ops_2b_since_1)
        .with_ops_applied(&ops_2c_since_1);

    assert_eq!(
        jmbl3.get_root().if_list().unwrap().val_to_litl(),
        Val::array([
            Val::str("hello"),
            Val::str("cheesy"),
            Val::str("dark"),
            Val::str("bright"),
            Val::str("world")
        ])
    );
}