rust-elm 0.1.0

Elm Architecture for Rust: composable reducers, pure effects, async runtime.
Documentation
use key_paths_derive::{Cp, Kp};
use rust_elm::{
    reducer::Reduce, Identifiable, IdentifiedVec, ScopeReducer, IfLetReducer, Cmd,
    Reducer, Effect,
};
use rust_key_paths::Kp as KpPath;

#[test]
fn identified_vec_ordering_and_remove() {
    #[derive(Debug, Clone, PartialEq, Eq)]
    struct Node {
        id: u32,
        label: &'static str,
    }

    impl Identifiable for Node {
        type Id = u32;
        fn id(&self) -> u32 {
            self.id
        }
    }

    let mut nodes = IdentifiedVec::new();
    nodes.insert(Node { id: 1, label: "a" });
    nodes.insert(Node { id: 2, label: "b" });
    nodes.reorder(1, 0);
    assert_eq!(nodes.iter().map(|n| n.id).collect::<Vec<_>>(), vec![2, 1]);
    nodes.remove(2);
    assert_eq!(nodes.len(), 1);
    assert_eq!(nodes.get(1).unwrap().label, "a");
}

#[test]
fn scope_child_state_isolated_from_sibling_field() {
    #[derive(Default)]
    struct Child {
        n: i32,
    }

    #[derive(Default)]
    struct Parent {
        child: Option<Child>,
        other: i32,
    }

    #[derive(Clone, Copy, Kp, Cp)]
    enum PA {
        Child(CA),
        TouchOther,
    }

    #[derive(Clone, Copy, Kp)]
    enum CA {
        Inc,
    }

    fn child_r(c: &mut Child, _: CA) -> Cmd<CA> {
        c.n += 1;
        Cmd::none()
    }

    fn child_get(p: &Parent) -> Option<&Child> {
        p.child.as_ref()
    }
    fn child_get_mut(p: &mut Parent) -> Option<&mut Child> {
        p.child.as_mut()
    }

    let scope = ScopeReducer::new(
        KpPath::new(child_get, child_get_mut),
        PA::child_cp(),
        1,
        Reduce::new(child_r),
    );
    let mut parent = Parent {
        child: Some(Child::default()),
        other: 5,
    };
    scope.reduce(&mut parent, PA::Child(CA::Inc));
    scope.reduce(&mut parent, PA::TouchOther);
    assert_eq!(parent.child.unwrap().n, 1);
    assert_eq!(parent.other, 5);
}

#[test]
fn if_let_dismiss_returns_cancel_effect() {
    #[derive(Default)]
    struct Child {
        n: i32,
    }

    #[derive(Default)]
    struct Parent {
        child: Option<Child>,
    }

    #[derive(Clone, Copy, Kp, Cp)]
    enum PA {
        Child(CA),
        Dismiss,
    }

    #[derive(Clone, Copy, Kp)]
    enum CA {
        Inc,
    }

    fn child_r(_: &mut Child, _: CA) -> Cmd<CA> {
        Cmd::none()
    }

    fn child_get(p: &Parent) -> Option<&Child> {
        p.child.as_ref()
    }
    fn child_get_mut(p: &mut Parent) -> Option<&mut Child> {
        p.child.as_mut()
    }

    let if_let = IfLetReducer::new(
        KpPath::new(child_get, child_get_mut),
        PA::child_cp(),
        |a| matches!(a, PA::Dismiss),
        |p| {
            p.child = None;
        },
        99,
        Reduce::new(child_r),
    );

    let mut parent = Parent {
        child: Some(Child::default()),
    };
    let cmd = if_let.reduce(&mut parent, PA::Dismiss);
    assert!(parent.child.is_none());
    assert!(matches!(
        cmd.into_effects().first(),
        Some(Effect::Cancel { id: 99 })
    ));
}

#[cfg(feature = "serde")]
#[test]
fn identified_vec_serde_round_trip() {
    #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
    struct Row {
        id: u64,
        n: i32,
    }

    impl Identifiable for Row {
        type Id = u64;
        fn id(&self) -> u64 {
            self.id
        }
    }

    let mut vec = IdentifiedVec::new();
    vec.insert(Row { id: 1, n: 1 });
    let json = serde_json::to_string(&vec).unwrap();
    let back: IdentifiedVec<u64, Row> = serde_json::from_str(&json).unwrap();
    assert_eq!(back.get(1).unwrap().n, 1);
}