jellyflow-runtime 0.1.0

Headless store, rules, schema, profile, and change pipeline for Jellyflow.
Documentation
use super::super::super::fixtures::{make_graph, make_store};

use crate::runtime::commit::NodeGraphPatch;
use crate::runtime::xyflow::callbacks::{
    ConnectionChange, EdgeConnection, NodeGraphCommitCallbacks, NodeGraphGestureCallbacks,
    NodeGraphViewCallbacks, SelectionChange, install_callbacks,
};
use crate::runtime::xyflow::changes::{EdgeChange, NodeChange};
use jellyflow_core::core::{
    CanvasPoint, Edge, EdgeId, EdgeKind, GroupId, Node, NodeId, NodeKindKey, Port, PortCapacity,
    PortDirection, PortId, PortKind,
};
use jellyflow_core::ops::{EdgeEndpoints, GraphOp, GraphTransaction};

#[test]
fn install_callbacks_receives_graph_and_view_events() {
    use std::cell::RefCell;
    use std::rc::Rc;

    #[derive(Clone)]
    struct Recorder {
        log: Rc<RefCell<Vec<&'static str>>>,
    }

    impl NodeGraphCommitCallbacks for Recorder {
        fn on_graph_commit(&mut self, _patch: &NodeGraphPatch) {
            self.log.borrow_mut().push("commit");
        }

        fn on_nodes_change(&mut self, _changes: &[NodeChange]) {
            self.log.borrow_mut().push("nodes");
        }

        fn on_edges_change(&mut self, _changes: &[EdgeChange]) {
            self.log.borrow_mut().push("edges");
        }

        fn on_connection_change(&mut self, _change: ConnectionChange) {
            self.log.borrow_mut().push("conn");
        }
    }

    impl NodeGraphViewCallbacks for Recorder {
        fn on_view_change(&mut self, _changes: &[crate::runtime::events::ViewChange]) {
            self.log.borrow_mut().push("view");
        }
    }

    impl NodeGraphGestureCallbacks for Recorder {}

    let (g0, a, _b, _out_port, _in_port, _eid) = make_graph();
    let mut store = make_store(g0);

    let log: Rc<RefCell<Vec<&'static str>>> = Rc::new(RefCell::new(Vec::new()));
    let recorder = Recorder { log: log.clone() };
    let _token = install_callbacks(&mut store, recorder);

    let tx = GraphTransaction::from_ops([GraphOp::SetNodePos {
        id: a,
        from: CanvasPoint { x: 0.0, y: 0.0 },
        to: CanvasPoint { x: 1.0, y: 2.0 },
    }]);
    let _ = store.dispatch_transaction(&tx).expect("dispatch");

    store.update_view_state(|s| {
        s.pan = CanvasPoint { x: 10.0, y: 20.0 };
    });

    let got = log.borrow().clone();
    assert!(got.contains(&"commit"));
    assert!(got.contains(&"nodes"));
    assert!(got.contains(&"view"));
}

#[test]
fn install_callbacks_calls_viewport_selection_and_connection_hooks() {
    use std::cell::RefCell;
    use std::rc::Rc;

    #[derive(Clone)]
    struct Recorder {
        log: Rc<RefCell<Vec<&'static str>>>,
    }

    impl NodeGraphCommitCallbacks for Recorder {
        fn on_connect(&mut self, _conn: EdgeConnection) {
            self.log.borrow_mut().push("connect");
        }

        fn on_disconnect(&mut self, _conn: EdgeConnection) {
            self.log.borrow_mut().push("disconnect");
        }

        fn on_reconnect(&mut self, _edge: EdgeId, _from: EdgeEndpoints, _to: EdgeEndpoints) {
            self.log.borrow_mut().push("reconnect");
        }
    }

    impl NodeGraphViewCallbacks for Recorder {
        fn on_viewport_change(&mut self, _pan: CanvasPoint, _zoom: f32) {
            self.log.borrow_mut().push("viewport");
        }

        fn on_selection_change(&mut self, _sel: SelectionChange) {
            self.log.borrow_mut().push("selection");
        }
    }

    impl NodeGraphGestureCallbacks for Recorder {}

    let (mut g0, a, _b, out_port, in_port, eid) = make_graph();

    let in2 = PortId::new();
    let c = NodeId::new();
    g0.nodes.insert(
        c,
        Node {
            kind: NodeKindKey::new("demo.c"),
            kind_version: 1,
            pos: CanvasPoint { x: 200.0, y: 0.0 },
            origin: None,
            selectable: None,
            focusable: None,
            draggable: None,
            connectable: None,
            deletable: None,
            parent: None,
            extent: None,
            expand_parent: None,
            size: None,
            hidden: false,
            collapsed: false,
            ports: vec![in2],
            data: serde_json::Value::Null,
        },
    );
    g0.ports.insert(
        in2,
        Port {
            node: c,
            key: jellyflow_core::core::PortKey::new("in2"),
            dir: PortDirection::In,
            kind: PortKind::Data,
            capacity: PortCapacity::Single,
            connectable: None,
            connectable_start: None,
            connectable_end: None,
            ty: None,
            data: serde_json::Value::Null,
        },
    );

    let mut store = make_store(g0);

    let log: Rc<RefCell<Vec<&'static str>>> = Rc::new(RefCell::new(Vec::new()));
    let recorder = Recorder { log: log.clone() };
    let _token = install_callbacks(&mut store, recorder);

    store.set_viewport(CanvasPoint { x: 10.0, y: 20.0 }, 1.25);
    store.set_selection(vec![a], vec![eid], vec![GroupId::new()]);

    let e2 = EdgeId::new();
    let tx_add = GraphTransaction::from_ops([GraphOp::AddEdge {
        id: e2,
        edge: Edge {
            kind: EdgeKind::Data,
            from: out_port,
            to: in_port,
            hidden: false,
            selectable: None,
            focusable: None,
            interaction_width: None,
            deletable: None,
            reconnectable: None,
        },
    }]);
    let _ = store.dispatch_transaction(&tx_add).expect("dispatch add");

    let tx_reconnect = GraphTransaction::from_ops([GraphOp::SetEdgeEndpoints {
        id: e2,
        from: EdgeEndpoints {
            from: out_port,
            to: in_port,
        },
        to: EdgeEndpoints {
            from: out_port,
            to: in2,
        },
    }]);
    let _ = store
        .dispatch_transaction(&tx_reconnect)
        .expect("dispatch reconnect");

    let tx_remove = GraphTransaction::from_ops([GraphOp::RemoveEdge {
        id: e2,
        edge: Edge {
            kind: EdgeKind::Data,
            from: out_port,
            to: in2,
            hidden: false,
            selectable: None,
            focusable: None,
            interaction_width: None,
            deletable: None,
            reconnectable: None,
        },
        bindings: Vec::new(),
    }]);
    let _ = store
        .dispatch_transaction(&tx_remove)
        .expect("dispatch remove");

    let got = log.borrow().clone();
    assert!(got.contains(&"viewport"));
    assert!(got.contains(&"selection"));
    assert!(got.contains(&"connect"));
    assert!(got.contains(&"reconnect"));
    assert!(got.contains(&"disconnect"));
}