jellyflow-core 0.2.0

Headless graph model, IDs, type descriptors, and interaction policy primitives for Jellyflow.
Documentation
use super::*;

#[test]
fn validate_allows_edges_regardless_of_port_direction() {
    let mut graph = Graph::default();
    let a = NodeId::new();
    let b = NodeId::new();
    graph.nodes.insert(a, make_node("core.a"));
    graph.nodes.insert(b, make_node("core.b"));

    let out_a = PortId::new();
    let out_b = PortId::new();
    attach_port(
        &mut graph,
        out_a,
        make_port(
            a,
            "out",
            PortDirection::Out,
            PortKind::Data,
            PortCapacity::Multi,
        ),
    );
    attach_port(
        &mut graph,
        out_b,
        make_port(
            b,
            "out",
            PortDirection::Out,
            PortKind::Data,
            PortCapacity::Multi,
        ),
    );

    let edge_id = EdgeId::new();
    graph.edges.insert(
        edge_id,
        Edge {
            kind: EdgeKind::Data,
            from: out_a,
            to: out_b,
            hidden: false,
            selectable: None,
            focusable: None,
            interaction_width: None,
            deletable: None,
            reconnectable: None,
        },
    );

    let report = validate_graph(&graph);
    assert!(report.is_ok());
}

#[test]
fn validate_rejects_port_missing_from_owner_ports() {
    let mut graph = Graph::default();
    let node_id = NodeId::new();
    graph.nodes.insert(node_id, make_node("core.a"));

    let port_id = PortId::new();
    graph.ports.insert(
        port_id,
        make_port(
            node_id,
            "out",
            PortDirection::Out,
            PortKind::Data,
            PortCapacity::Multi,
        ),
    );

    let report = validate_graph(&graph);
    assert!(report.errors.iter().any(|e| matches!(
        e,
        GraphValidationError::PortMissingFromOwner { port, node }
            if *port == port_id && *node == node_id
    )));
}

#[test]
fn validate_rejects_node_with_missing_parent_group() {
    let mut graph = Graph::default();
    let n = NodeId::new();
    let mut node = make_node("core.a");
    node.parent = Some(GroupId::new());
    graph.nodes.insert(n, node);

    let report = validate_graph(&graph);
    assert!(report.errors.iter().any(|e| matches!(
        e,
        GraphValidationError::NodeParentMissingGroup { node, .. } if *node == n
    )));
}

#[test]
fn validate_rejects_node_with_invalid_size() {
    let mut graph = Graph::default();
    let n = NodeId::new();
    let mut node = make_node("core.a");
    node.size = Some(CanvasSize {
        width: -1.0,
        height: 10.0,
    });
    graph.nodes.insert(n, node);

    let report = validate_graph(&graph);
    assert!(report.errors.iter().any(|e| matches!(
        e,
        GraphValidationError::NodeInvalidSize { node, .. } if *node == n
    )));
}

#[test]
fn validate_rejects_edge_kind_that_does_not_match_port_kind() {
    let mut graph = Graph::default();
    let a = NodeId::new();
    let b = NodeId::new();
    graph.nodes.insert(a, make_node("core.a"));
    graph.nodes.insert(b, make_node("core.b"));

    let out_a = PortId::new();
    let in_b = PortId::new();
    attach_port(
        &mut graph,
        out_a,
        make_port(
            a,
            "out",
            PortDirection::Out,
            PortKind::Data,
            PortCapacity::Multi,
        ),
    );
    attach_port(
        &mut graph,
        in_b,
        make_port(
            b,
            "in",
            PortDirection::In,
            PortKind::Data,
            PortCapacity::Single,
        ),
    );

    let edge_id = EdgeId::new();
    graph.edges.insert(
        edge_id,
        Edge {
            kind: EdgeKind::Exec,
            from: out_a,
            to: in_b,
            hidden: false,
            selectable: None,
            focusable: None,
            interaction_width: None,
            deletable: None,
            reconnectable: None,
        },
    );

    let report = validate_graph(&graph);
    assert!(!report.is_ok());
}

#[test]
fn validate_reports_both_missing_edge_endpoints() {
    let mut graph = Graph::default();
    let edge_id = EdgeId::new();
    let from = PortId::new();
    let to = PortId::new();
    graph.edges.insert(
        edge_id,
        Edge {
            kind: EdgeKind::Data,
            from,
            to,
            hidden: false,
            selectable: None,
            focusable: None,
            interaction_width: None,
            deletable: None,
            reconnectable: None,
        },
    );

    let report = validate_graph(&graph);
    let missing_ports = report
        .errors()
        .iter()
        .filter_map(|err| match err {
            GraphValidationError::EdgeMissingPort { edge, port } if *edge == edge_id => Some(*port),
            _ => None,
        })
        .collect::<Vec<_>>();

    assert_eq!(missing_ports, vec![from, to]);
}

#[test]
fn validate_reports_duplicate_edges() {
    let mut graph = Graph::default();
    let a = NodeId::new();
    let b = NodeId::new();
    graph.nodes.insert(a, make_node("core.a"));
    graph.nodes.insert(b, make_node("core.b"));

    let out_a = PortId::new();
    let in_b = PortId::new();
    attach_port(
        &mut graph,
        out_a,
        make_port(
            a,
            "out",
            PortDirection::Out,
            PortKind::Data,
            PortCapacity::Multi,
        ),
    );
    attach_port(
        &mut graph,
        in_b,
        make_port(
            b,
            "in",
            PortDirection::In,
            PortKind::Data,
            PortCapacity::Multi,
        ),
    );

    let first = EdgeId::new();
    let duplicate = EdgeId::new();
    for edge_id in [first, duplicate] {
        graph.edges.insert(
            edge_id,
            Edge {
                kind: EdgeKind::Data,
                from: out_a,
                to: in_b,
                hidden: false,
                selectable: None,
                focusable: None,
                interaction_width: None,
                deletable: None,
                reconnectable: None,
            },
        );
    }

    let report = validate_graph(&graph);
    let duplicate_edges = report
        .errors()
        .iter()
        .filter_map(|err| match err {
            GraphValidationError::DuplicateEdge { edge } => Some(*edge),
            _ => None,
        })
        .collect::<Vec<_>>();

    assert_eq!(duplicate_edges.len(), 1);
    assert!([first, duplicate].contains(&duplicate_edges[0]));
}

#[test]
fn validate_reports_single_port_capacity_exceeded() {
    let mut graph = Graph::default();
    let a = NodeId::new();
    let b = NodeId::new();
    let c = NodeId::new();
    graph.nodes.insert(a, make_node("core.a"));
    graph.nodes.insert(b, make_node("core.b"));
    graph.nodes.insert(c, make_node("core.c"));

    let out_a = PortId::new();
    let out_c = PortId::new();
    let in_b = PortId::new();
    attach_port(
        &mut graph,
        out_a,
        make_port(
            a,
            "out",
            PortDirection::Out,
            PortKind::Data,
            PortCapacity::Multi,
        ),
    );
    attach_port(
        &mut graph,
        out_c,
        make_port(
            c,
            "out",
            PortDirection::Out,
            PortKind::Data,
            PortCapacity::Multi,
        ),
    );
    attach_port(
        &mut graph,
        in_b,
        make_port(
            b,
            "in",
            PortDirection::In,
            PortKind::Data,
            PortCapacity::Single,
        ),
    );

    for from in [out_a, out_c] {
        graph.edges.insert(
            EdgeId::new(),
            Edge {
                kind: EdgeKind::Data,
                from,
                to: in_b,
                hidden: false,
                selectable: None,
                focusable: None,
                interaction_width: None,
                deletable: None,
                reconnectable: None,
            },
        );
    }

    let report = validate_graph(&graph);

    assert!(report.errors().iter().any(|err| matches!(
        err,
        GraphValidationError::PortCapacityExceeded { port, count: 2, .. } if *port == in_b
    )));
}