jellyflow-runtime 0.3.0

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

use crate::runtime::lookups::ConnectionSide;
use crate::runtime::measurement::{MeasuredSurfaceAnchor, NodeMeasurement};
use jellyflow_core::core::{
    CanvasPoint, CanvasRect, CanvasSize, Edge, EdgeKind, EdgeReconnectable, Group, GroupId,
    NodeOrigin, PortKind,
};
use jellyflow_core::ops::{GraphOp, GraphTransaction};

#[test]
fn store_lookups_update_after_dispatch_transaction() {
    let (mut g, _a, _b, out_port, in_port, eid) = make_graph();
    fixture_clear_edges(&mut g);

    let mut store = make_store(g);
    assert!(store.lookups().edge_lookup.is_empty());

    let tx = GraphTransaction::from_ops([GraphOp::AddEdge {
        id: eid,
        edge: Edge::new(EdgeKind::Data, out_port, in_port),
    }]);

    store.dispatch_transaction(&tx).expect("dispatch");
    assert!(store.lookups().edge_lookup.contains_key(&eid));
}

#[test]
fn store_lookups_update_node_hidden_after_dispatch_transaction() {
    let (g, a, _b, _out_port, _in_port, _eid) = make_graph();
    let mut store = make_store(g);
    assert!(!store.lookups().node_lookup.get(&a).unwrap().hidden);

    let tx = GraphTransaction::from_ops([GraphOp::SetNodeHidden {
        id: a,
        from: false,
        to: true,
    }]);

    store.dispatch_transaction(&tx).expect("dispatch");
    assert!(store.lookups().node_lookup.get(&a).unwrap().hidden);
}

#[test]
fn store_lookups_update_node_origin_after_dispatch_transaction() {
    let (g, a, _b, _out_port, _in_port, _eid) = make_graph();
    let mut store = make_store(g);
    assert_eq!(store.lookups().node_lookup.get(&a).unwrap().origin, None);

    let origin = NodeOrigin { x: 0.5, y: 0.25 };
    let tx = GraphTransaction::from_ops([GraphOp::SetNodeOrigin {
        id: a,
        from: None,
        to: Some(origin),
    }]);

    store.dispatch_transaction(&tx).expect("dispatch");
    assert_eq!(
        store.lookups().node_lookup.get(&a).unwrap().origin,
        Some(origin)
    );
}

#[test]
fn store_lookups_update_edge_reconnectable_after_dispatch_transaction() {
    let (g, _a, _b, _out_port, _in_port, eid) = make_graph();
    let mut store = make_store(g);
    assert_eq!(
        store.lookups().edge_lookup.get(&eid).unwrap().reconnectable,
        None
    );

    let tx = GraphTransaction::from_ops([GraphOp::SetEdgeReconnectable {
        id: eid,
        from: None,
        to: Some(EdgeReconnectable::Bool(false)),
    }]);

    store.dispatch_transaction(&tx).expect("dispatch");
    assert_eq!(
        store.lookups().edge_lookup.get(&eid).unwrap().reconnectable,
        Some(EdgeReconnectable::Bool(false))
    );
}

#[test]
fn store_lookups_update_edge_kind_in_connection_lookup_after_dispatch_transaction() {
    let (mut g, a, b, out_port, in_port, eid) = make_graph();
    g.update_port(&out_port, |port| port.kind = PortKind::Exec)
        .expect("port exists");
    g.update_port(&in_port, |port| port.kind = PortKind::Exec)
        .expect("port exists");
    let mut store = make_store(g);

    let tx = GraphTransaction::from_ops([GraphOp::SetEdgeKind {
        id: eid,
        from: EdgeKind::Data,
        to: EdgeKind::Exec,
    }]);

    store.dispatch_transaction(&tx).expect("dispatch");

    assert_eq!(
        store.lookups().edge_lookup.get(&eid).unwrap().kind,
        EdgeKind::Exec
    );
    assert_eq!(
        store
            .lookups()
            .connections_for_port(a, ConnectionSide::Source, out_port)
            .expect("source connections")
            .get(&eid)
            .unwrap()
            .kind,
        EdgeKind::Exec
    );
    assert_eq!(
        store
            .lookups()
            .connections_for_port(b, ConnectionSide::Target, in_port)
            .expect("target connections")
            .get(&eid)
            .unwrap()
            .kind,
        EdgeKind::Exec
    );
}

#[test]
fn store_lookups_remove_port_updates_node_ports_and_incident_edges() {
    let (g, a, _b, out_port, _in_port, eid) = make_graph();
    let port = g.ports().get(&out_port).expect("port").clone();
    let edge = g.edges().get(&eid).expect("edge").clone();
    let mut store = make_store(g);

    assert!(
        store
            .lookups()
            .node_lookup
            .get(&a)
            .unwrap()
            .ports
            .contains(&out_port)
    );
    assert!(store.lookups().edge_lookup.contains_key(&eid));

    let tx = GraphTransaction::from_ops([GraphOp::RemovePort {
        id: out_port,
        port,
        edges: vec![(eid, edge)],
        bindings: Vec::new(),
    }]);

    store.dispatch_transaction(&tx).expect("dispatch");
    assert!(
        !store
            .lookups()
            .node_lookup
            .get(&a)
            .unwrap()
            .ports
            .contains(&out_port)
    );
    assert!(!store.lookups().edge_lookup.contains_key(&eid));
}

#[test]
fn store_lookups_remove_port_prunes_measured_semantic_anchors() {
    let (g, a, _b, out_port, _in_port, eid) = make_graph();
    let port = g.ports().get(&out_port).expect("port").clone();
    let edge = g.edges().get(&eid).expect("edge").clone();
    let mut store = make_store(g);

    store
        .report_node_measurement(
            NodeMeasurement::new(a)
                .with_size(Some(CanvasSize {
                    width: 120.0,
                    height: 80.0,
                }))
                .with_anchors([MeasuredSurfaceAnchor::new(
                    "field.out.output",
                    CanvasRect {
                        origin: CanvasPoint { x: 110.0, y: 20.0 },
                        size: CanvasSize {
                            width: 10.0,
                            height: 20.0,
                        },
                    },
                    crate::runtime::geometry::HandlePosition::Right,
                )
                .with_port(out_port)
                .with_port_key("out")]),
        )
        .expect("node measurement");
    assert_eq!(
        store
            .node_measurement(a)
            .expect("measurement")
            .anchors
            .len(),
        1
    );

    let tx = GraphTransaction::from_ops([GraphOp::RemovePort {
        id: out_port,
        port,
        edges: vec![(eid, edge)],
        bindings: Vec::new(),
    }]);

    store.dispatch_transaction(&tx).expect("dispatch");
    assert!(
        store
            .node_measurement(a)
            .expect("measurement survives via measured size")
            .anchors
            .is_empty()
    );
}

#[test]
fn store_lookups_remove_group_clears_detached_node_parent() {
    let (mut g, a, _b, _out_port, _in_port, _eid) = make_graph();
    let group_id = GroupId::new();
    let group = Group {
        title: "Group".to_string(),
        rect: CanvasRect {
            origin: CanvasPoint { x: -10.0, y: -10.0 },
            size: CanvasSize {
                width: 200.0,
                height: 100.0,
            },
        },
        color: None,
    };
    fixture_insert_group(&mut g, group_id, group.clone());
    g.update_node(&a, |node| node.parent = Some(group_id))
        .expect("node exists");

    let mut store = make_store(g);
    assert_eq!(
        store.lookups().node_lookup.get(&a).unwrap().parent,
        Some(group_id)
    );

    let tx = GraphTransaction::from_ops([GraphOp::RemoveGroup {
        id: group_id,
        group,
        detached: vec![(a, Some(group_id))],
        bindings: Vec::new(),
    }]);

    store.dispatch_transaction(&tx).expect("dispatch");
    assert_eq!(store.lookups().node_lookup.get(&a).unwrap().parent, None);
}