jellyflow-runtime 0.1.0

Headless store, rules, schema, profile, and change pipeline for Jellyflow.
Documentation
use crate::runtime::binding::{
    BindingEndpointResolution, BindingEndpointResolutionStatus, BindingQueryOptions,
};
use crate::runtime::measurement::NodeMeasurement;
use crate::runtime::tests::fixtures::make_store;
use jellyflow_core::core::{
    Binding, BindingEndpoint, BindingId, CanvasPoint, CanvasSize, Graph, GraphId,
    GraphLocalBindingTarget, Node, NodeId, NodeKindKey, SourceAnchor,
};
use jellyflow_core::ops::{GraphOp, GraphTransaction};

#[test]
fn binding_query_resolves_node_anchor_from_runtime_measurement() {
    let node = NodeId::from_u128(1);
    let binding = BindingId::from_u128(10);
    let mut store = make_store(graph_with_source_binding(node, binding));

    assert_eq!(
        store
            .binding_query()
            .binding(binding)
            .unwrap()
            .subject
            .status(),
        BindingEndpointResolutionStatus::Unresolved
    );

    store
        .report_node_measurement(NodeMeasurement::new(node).with_size(Some(CanvasSize {
            width: 100.0,
            height: 40.0,
        })))
        .expect("report measurement");

    let query = store.binding_query();
    let resolved = query.binding(binding).expect("binding");
    assert_eq!(query.revision, store.layout_facts_revision());
    assert!(matches!(
        resolved.subject.resolution,
        BindingEndpointResolution::NodeRect {
            node: resolved_node,
            rect,
            center,
        } if resolved_node == node
            && rect.origin == CanvasPoint { x: 10.0, y: 20.0 }
            && rect.size == CanvasSize { width: 100.0, height: 40.0 }
            && center == CanvasPoint { x: 60.0, y: 40.0 }
    ));
    assert!(matches!(
        resolved.target.resolution,
        BindingEndpointResolution::Source
    ));
}

#[test]
fn binding_query_marks_hidden_node_targets_unless_requested() {
    let node = NodeId::from_u128(2);
    let binding = BindingId::from_u128(20);
    let mut graph = graph_with_source_binding(node, binding);
    graph.nodes.get_mut(&node).unwrap().hidden = true;
    graph.nodes.get_mut(&node).unwrap().size = Some(CanvasSize {
        width: 10.0,
        height: 10.0,
    });
    let store = make_store(graph);

    assert_eq!(
        store
            .binding_query()
            .binding(binding)
            .unwrap()
            .subject
            .status(),
        BindingEndpointResolutionStatus::Hidden
    );
    assert_eq!(
        store
            .binding_query_with_options(BindingQueryOptions::default().include_hidden(true))
            .binding(binding)
            .unwrap()
            .subject
            .status(),
        BindingEndpointResolutionStatus::Resolved
    );
}

#[test]
fn binding_query_updates_after_binding_dispatch() {
    let node = NodeId::from_u128(3);
    let binding = BindingId::from_u128(30);
    let mut graph = Graph::new(GraphId::from_u128(1));
    graph.nodes.insert(node, node_fixture());
    graph.nodes.get_mut(&node).unwrap().size = Some(CanvasSize {
        width: 10.0,
        height: 10.0,
    });
    let mut store = make_store(graph);

    assert!(store.binding_query().binding(binding).is_none());

    store
        .dispatch_transaction(&GraphTransaction::from_ops([GraphOp::AddBinding {
            id: binding,
            binding: source_binding(node),
        }]))
        .expect("dispatch binding");

    assert!(store.binding_query().binding(binding).is_some());
}

#[test]
fn layout_context_with_binding_pins_uses_resolved_node_binding_targets() {
    let node = NodeId::from_u128(4);
    let binding = BindingId::from_u128(40);
    let mut graph = graph_with_source_binding(node, binding);
    graph.nodes.get_mut(&node).unwrap().size = Some(CanvasSize {
        width: 10.0,
        height: 10.0,
    });
    let store = make_store(graph);

    let context = store.layout_context_with_binding_pins();

    assert!(context.pinned_nodes.contains(&node));
}

fn graph_with_source_binding(node: NodeId, binding: BindingId) -> Graph {
    let mut graph = Graph::new(GraphId::from_u128(1));
    graph.nodes.insert(
        node,
        Node {
            pos: CanvasPoint { x: 10.0, y: 20.0 },
            ..node_fixture()
        },
    );
    graph.bindings.insert(binding, source_binding(node));
    graph
}

fn source_binding(node: NodeId) -> Binding {
    Binding {
        subject: BindingEndpoint::graph_local(GraphLocalBindingTarget::Node { id: node }),
        target: BindingEndpoint::source(SourceAnchor::new(
            "source.pdf",
            serde_json::json!({ "page": 1 }),
        )),
        kind: Some("excerpt".to_string()),
        meta: serde_json::Value::Null,
    }
}

fn node_fixture() -> Node {
    Node {
        kind: NodeKindKey::new("test.node"),
        kind_version: 1,
        pos: CanvasPoint::default(),
        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::new(),
        data: serde_json::Value::Null,
    }
}