jellyflow-runtime 0.1.0

Headless store, rules, schema, profile, and change pipeline for Jellyflow.
Documentation
use super::super::harness::{HarnessEvent, InteractionHarness};
use super::support::drag_fixture;

use crate::io::NodeGraphViewState;
use crate::runtime::drag::{NODE_DRAG_TRANSACTION_LABEL, NodeDragItem, NodeDragRequest};
use jellyflow_core::core::{CanvasPoint, CanvasRect, CanvasSize, Group, GroupId, NodeExtent};
use jellyflow_core::ops::GraphOp;

#[test]
fn drag_parent_expansion_keeps_parent_extent_clamp_when_disabled() {
    let mut fixture = drag_fixture();
    let child = fixture.child_in_selected_group;
    let parent = fixture.selected_group;
    let parent_rect = fixture.graph.groups[&parent].rect;
    let node = fixture.graph.nodes.get_mut(&child).unwrap();
    node.size = Some(CanvasSize {
        width: 20.0,
        height: 20.0,
    });
    node.extent = Some(NodeExtent::Parent);
    node.expand_parent = Some(false);

    let mut harness = InteractionHarness::new("parent extent clamp", fixture.graph);
    let plan = harness
        .store()
        .plan_node_drag(NodeDragRequest {
            node: child,
            to: CanvasPoint { x: 350.0, y: 50.0 },
        })
        .expect("parent extent drag plan");

    assert_eq!(plan.to, CanvasPoint { x: 340.0, y: 40.0 });
    assert_eq!(
        plan.items(),
        &[NodeDragItem {
            node: child,
            from: CanvasPoint { x: 300.0, y: 0.0 },
            to: CanvasPoint { x: 340.0, y: 40.0 },
        }],
    );
    assert!(
        matches!(
            plan.transaction().ops(),
            [GraphOp::SetNodePos { id, from, to }]
                if *id == child
                    && *from == CanvasPoint { x: 300.0, y: 0.0 }
                    && *to == CanvasPoint { x: 340.0, y: 40.0 }
        ),
        "disabled parent expansion should only clamp the child: {:#?}",
        plan.transaction().ops(),
    );

    harness
        .store_mut()
        .apply_node_drag(NodeDragRequest {
            node: child,
            to: CanvasPoint { x: 350.0, y: 50.0 },
        })
        .expect("parent extent drag dispatch succeeds")
        .expect("parent extent drag dispatch commits");

    assert_eq!(harness.store().graph().groups[&parent].rect, parent_rect);
    assert_eq!(
        harness.store().graph().nodes[&child].pos,
        CanvasPoint { x: 340.0, y: 40.0 },
    );
    harness.assert_events(&[HarnessEvent::graph_commit(
        Some(NODE_DRAG_TRANSACTION_LABEL),
        ["set_node_pos"],
    )]);
}

#[test]
fn drag_parent_expansion_expands_parent_group_when_enabled() {
    let mut fixture = drag_fixture();
    let child = fixture.child_in_selected_group;
    let parent = fixture.selected_group;
    let parent_rect = fixture.graph.groups[&parent].rect;
    let node = fixture.graph.nodes.get_mut(&child).unwrap();
    node.size = Some(CanvasSize {
        width: 20.0,
        height: 20.0,
    });
    node.extent = Some(NodeExtent::Parent);
    node.expand_parent = Some(true);

    let expected_parent_rect = CanvasRect {
        origin: CanvasPoint { x: 280.0, y: -20.0 },
        size: CanvasSize {
            width: 90.0,
            height: 90.0,
        },
    };
    let mut harness = InteractionHarness::new("parent expansion", fixture.graph);
    let target = CanvasPoint { x: 350.0, y: 50.0 };

    let plan = harness
        .store()
        .plan_node_drag(NodeDragRequest {
            node: child,
            to: target,
        })
        .expect("parent expansion drag plan");

    assert_eq!(plan.to, target);
    assert_eq!(
        plan.items(),
        &[NodeDragItem {
            node: child,
            from: CanvasPoint { x: 300.0, y: 0.0 },
            to: target,
        }],
    );
    assert!(
        matches!(
            plan.transaction().ops(),
            [
                GraphOp::SetNodePos {
                    id: moved,
                    from: child_from,
                    to: child_to,
                },
                GraphOp::SetGroupRect {
                    id: expanded,
                    from: group_from,
                    to: group_to,
                },
            ] if *moved == child
                && *child_from == CanvasPoint { x: 300.0, y: 0.0 }
                && *child_to == target
                && *expanded == parent
                && *group_from == parent_rect
                && *group_to == expected_parent_rect
        ),
        "enabled parent expansion should move child and expand parent: {:#?}",
        plan.transaction().ops(),
    );

    harness
        .store_mut()
        .apply_node_drag(NodeDragRequest {
            node: child,
            to: target,
        })
        .expect("parent expansion dispatch succeeds")
        .expect("parent expansion dispatch commits");

    assert_eq!(
        harness.store().graph().groups[&parent].rect,
        expected_parent_rect,
    );
    assert_eq!(harness.store().graph().nodes[&child].pos, target);
    harness.assert_events(&[HarnessEvent::graph_commit(
        Some(NODE_DRAG_TRANSACTION_LABEL),
        ["set_node_pos", "set_group_rect"],
    )]);
}

#[test]
fn drag_parent_expansion_expands_multiple_parent_groups_in_sorted_order() {
    let mut fixture = drag_fixture();
    let first_child = fixture.child_in_selected_group;
    let first_parent = fixture.selected_group;
    let second_child = fixture.enabled;
    let second_parent = GroupId::from_u128(101);
    fixture.graph.groups.insert(
        second_parent,
        Group {
            title: "Second Group".to_owned(),
            rect: CanvasRect {
                origin: CanvasPoint { x: 0.0, y: 0.0 },
                size: CanvasSize {
                    width: 70.0,
                    height: 80.0,
                },
            },
            color: None,
        },
    );

    let first = fixture.graph.nodes.get_mut(&first_child).unwrap();
    first.size = Some(CanvasSize {
        width: 20.0,
        height: 20.0,
    });
    first.extent = Some(NodeExtent::Parent);
    first.expand_parent = Some(true);

    let second = fixture.graph.nodes.get_mut(&second_child).unwrap();
    second.parent = Some(second_parent);
    second.size = Some(CanvasSize {
        width: 30.0,
        height: 20.0,
    });
    second.extent = Some(NodeExtent::Parent);
    second.expand_parent = Some(true);

    let mut view_state = NodeGraphViewState::default();
    view_state.set_selection(vec![second_child], Vec::new(), Vec::new());
    let harness =
        InteractionHarness::with_view_state("multi parent expansion", fixture.graph, view_state);
    let target = CanvasPoint { x: 350.0, y: 50.0 };

    let plan = harness
        .store()
        .plan_node_drag(NodeDragRequest {
            node: first_child,
            to: target,
        })
        .expect("multi parent expansion drag plan");

    assert!(
        matches!(
            plan.transaction().ops(),
            [
                GraphOp::SetNodePos {
                    id: moved_second,
                    to: second_to,
                    ..
                },
                GraphOp::SetNodePos {
                    id: moved_first,
                    to: first_to,
                    ..
                },
                GraphOp::SetGroupRect {
                    id: expanded_first,
                    to: first_rect,
                    ..
                },
                GraphOp::SetGroupRect {
                    id: expanded_second,
                    to: second_rect,
                    ..
                },
            ] if *moved_second == second_child
                && *second_to == CanvasPoint { x: 60.0, y: 70.0 }
                && *moved_first == first_child
                && *first_to == target
                && *expanded_first == first_parent
                && *first_rect == CanvasRect {
                    origin: CanvasPoint { x: 280.0, y: -20.0 },
                    size: CanvasSize {
                        width: 90.0,
                        height: 90.0,
                    },
                }
                && *expanded_second == second_parent
                && *second_rect == CanvasRect {
                    origin: CanvasPoint { x: 0.0, y: 0.0 },
                    size: CanvasSize {
                        width: 90.0,
                        height: 90.0,
                    },
                }
        ),
        "multi-parent expansion should sort node ops by node id and group ops by group id: {:#?}",
        plan.transaction().ops(),
    );
}

#[test]
fn drag_parent_expansion_left_top_preserves_absolute_sibling_positions_without_compensation() {
    let mut fixture = drag_fixture();
    let child = fixture.child_in_selected_group;
    let sibling = fixture.enabled;
    let parent = fixture.selected_group;
    let child_node = fixture.graph.nodes.get_mut(&child).unwrap();
    child_node.size = Some(CanvasSize {
        width: 20.0,
        height: 20.0,
    });
    child_node.extent = Some(NodeExtent::Parent);
    child_node.expand_parent = Some(true);

    let sibling_pos = CanvasPoint { x: 320.0, y: 10.0 };
    let sibling_node = fixture.graph.nodes.get_mut(&sibling).unwrap();
    sibling_node.parent = Some(parent);
    sibling_node.pos = sibling_pos;

    let mut harness = InteractionHarness::new("left top parent expansion", fixture.graph);
    let target = CanvasPoint { x: 270.0, y: -30.0 };
    let expected_parent_rect = CanvasRect {
        origin: CanvasPoint { x: 270.0, y: -30.0 },
        size: CanvasSize {
            width: 90.0,
            height: 90.0,
        },
    };

    let plan = harness
        .store()
        .plan_node_drag(NodeDragRequest {
            node: child,
            to: target,
        })
        .expect("left top parent expansion drag plan");

    assert!(
        matches!(
            plan.transaction().ops(),
            [
                GraphOp::SetNodePos {
                    id: moved,
                    to: child_to,
                    ..
                },
                GraphOp::SetGroupRect {
                    id: expanded,
                    to: group_to,
                    ..
                },
            ] if *moved == child
                && *child_to == target
                && *expanded == parent
                && *group_to == expected_parent_rect
        ),
        "left/top expansion should not add sibling compensation ops for absolute Jellyflow coordinates: {:#?}",
        plan.transaction().ops(),
    );

    harness
        .store_mut()
        .apply_node_drag(NodeDragRequest {
            node: child,
            to: target,
        })
        .expect("left top parent expansion dispatch succeeds")
        .expect("left top parent expansion dispatch commits");

    assert_eq!(
        harness.store().graph().groups[&parent].rect,
        expected_parent_rect,
    );
    assert_eq!(harness.store().graph().nodes[&child].pos, target);
    assert_eq!(harness.store().graph().nodes[&sibling].pos, sibling_pos);
    harness.assert_events(&[HarnessEvent::graph_commit(
        Some(NODE_DRAG_TRANSACTION_LABEL),
        ["set_node_pos", "set_group_rect"],
    )]);
}