use super::super::super::fixtures::{make_graph, make_store};
use crate::runtime::commit::NodeGraphPatch;
use crate::runtime::xyflow::ControlledGraph;
use crate::runtime::xyflow::callbacks::{
DeleteChange, EdgeConnection, NodeGraphCommitCallbacks, NodeGraphGestureCallbacks,
NodeGraphViewCallbacks, install_callbacks,
};
use crate::runtime::xyflow::changes::{EdgeChange, NodeChange, NodeGraphChanges};
use jellyflow_core::core::{
CanvasPoint, CanvasRect, CanvasSize, EdgeId, EdgeReconnectable, Group, GroupId, NodeId,
StickyNote, StickyNoteId,
};
use jellyflow_core::ops::{GraphOp, GraphOpBuilderExt, GraphTransaction};
#[test]
fn install_callbacks_receives_full_patch_for_port_only_commits() {
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Clone)]
struct Recorder {
saw_port_patch: Rc<RefCell<bool>>,
node_edge_counts: Rc<RefCell<Vec<(usize, usize)>>>,
}
impl NodeGraphCommitCallbacks for Recorder {
fn on_graph_commit(&mut self, patch: &NodeGraphPatch) {
*self.saw_port_patch.borrow_mut() = patch
.ops()
.iter()
.any(|op| matches!(op, GraphOp::SetPortData { .. }));
}
fn on_node_edge_changes(&mut self, changes: &NodeGraphChanges) {
self.node_edge_counts
.borrow_mut()
.push((changes.nodes().len(), changes.edges().len()));
}
}
impl NodeGraphViewCallbacks for Recorder {}
impl NodeGraphGestureCallbacks for Recorder {}
let (g0, _a, _b, out_port, _in_port, _eid) = make_graph();
let mut store = make_store(g0);
let saw_port_patch = Rc::new(RefCell::new(false));
let node_edge_counts = Rc::new(RefCell::new(Vec::new()));
let recorder = Recorder {
saw_port_patch: saw_port_patch.clone(),
node_edge_counts: node_edge_counts.clone(),
};
let _token = install_callbacks(&mut store, recorder);
let tx = GraphTransaction::from_ops([GraphOp::SetPortData {
id: out_port,
from: serde_json::Value::Null,
to: serde_json::json!({ "unit": "kg" }),
}])
.with_label("Port Data");
let outcome = store.dispatch_transaction(&tx).expect("dispatch");
assert!(matches!(
outcome.patch.ops().first(),
Some(GraphOp::SetPortData { id, .. }) if *id == out_port
));
let node_edge_changes = NodeGraphChanges::from_patch(&outcome.patch);
assert!(node_edge_changes.nodes().is_empty());
assert!(node_edge_changes.edges().is_empty());
assert!(*saw_port_patch.borrow());
assert_eq!(&*node_edge_counts.borrow(), &[(0, 0)]);
}
#[test]
fn controlled_graph_can_apply_store_changes_via_callbacks() {
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Clone)]
struct ControlledApply {
graph: Rc<RefCell<ControlledGraph>>,
}
impl NodeGraphCommitCallbacks for ControlledApply {
fn on_nodes_change(&mut self, changes: &[NodeChange]) {
self.graph.borrow_mut().apply_node_changes(changes);
}
fn on_edges_change(&mut self, changes: &[EdgeChange]) {
self.graph.borrow_mut().apply_edge_changes(changes);
}
}
impl NodeGraphViewCallbacks for ControlledApply {}
impl NodeGraphGestureCallbacks for ControlledApply {}
let (g0, a, _b, _out_port, _in_port, eid) = make_graph();
let mut store = make_store(g0.clone());
let controlled = Rc::new(RefCell::new(ControlledGraph::new(g0)));
let _token = install_callbacks(
&mut store,
ControlledApply {
graph: controlled.clone(),
},
);
let tx = GraphTransaction::from_ops([
GraphOp::SetNodePos {
id: a,
from: CanvasPoint { x: 0.0, y: 0.0 },
to: CanvasPoint { x: 123.0, y: 456.0 },
},
GraphOp::SetNodeHidden {
id: a,
from: false,
to: true,
},
GraphOp::SetEdgeReconnectable {
id: eid,
from: None,
to: Some(EdgeReconnectable::Bool(false)),
},
]);
let _ = store.dispatch_transaction(&tx).expect("dispatch");
let store_json = serde_json::to_value(store.graph()).expect("store json");
let controlled_json =
serde_json::to_value(controlled.borrow().graph()).expect("controlled json");
assert_eq!(store_json, controlled_json);
}
#[test]
fn install_callbacks_preserves_commit_callback_order_for_controlled_updates() {
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Clone)]
struct Recorder {
order: Rc<RefCell<Vec<&'static str>>>,
}
impl NodeGraphCommitCallbacks for Recorder {
fn on_graph_commit(&mut self, _patch: &NodeGraphPatch) {
self.order.borrow_mut().push("graph_commit");
}
fn on_node_edge_changes(&mut self, _changes: &NodeGraphChanges) {
self.order.borrow_mut().push("node_edge_changes");
}
fn on_nodes_change(&mut self, _changes: &[NodeChange]) {
self.order.borrow_mut().push("nodes_change");
}
fn on_edges_change(&mut self, _changes: &[EdgeChange]) {
self.order.borrow_mut().push("edges_change");
}
}
impl NodeGraphViewCallbacks for Recorder {}
impl NodeGraphGestureCallbacks for Recorder {}
let (g0, a, _b, _out_port, _in_port, eid) = make_graph();
let mut store = make_store(g0);
let order = Rc::new(RefCell::new(Vec::new()));
let _token = install_callbacks(
&mut store,
Recorder {
order: order.clone(),
},
);
let tx = GraphTransaction::from_ops([
GraphOp::SetNodePos {
id: a,
from: CanvasPoint { x: 0.0, y: 0.0 },
to: CanvasPoint { x: 123.0, y: 456.0 },
},
GraphOp::SetEdgeReconnectable {
id: eid,
from: None,
to: Some(EdgeReconnectable::Bool(false)),
},
]);
let _ = store.dispatch_transaction(&tx).expect("dispatch");
assert_eq!(
order.borrow().as_slice(),
&[
"graph_commit",
"node_edge_changes",
"nodes_change",
"edges_change",
],
);
}
#[test]
fn install_callbacks_calls_delete_hooks_for_removed_resources() {
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Clone)]
struct Recorder {
nodes_deleted: Rc<RefCell<Vec<NodeId>>>,
edges_deleted: Rc<RefCell<Vec<EdgeId>>>,
groups_deleted: Rc<RefCell<Vec<GroupId>>>,
sticky_notes_deleted: Rc<RefCell<Vec<StickyNoteId>>>,
deleted: Rc<RefCell<Vec<DeleteChange>>>,
disconnected: Rc<RefCell<Vec<EdgeId>>>,
}
impl NodeGraphCommitCallbacks for Recorder {
fn on_nodes_delete(&mut self, nodes: &[NodeId]) {
self.nodes_deleted.borrow_mut().extend_from_slice(nodes);
}
fn on_edges_delete(&mut self, edges: &[EdgeId]) {
self.edges_deleted.borrow_mut().extend_from_slice(edges);
}
fn on_groups_delete(&mut self, groups: &[GroupId]) {
self.groups_deleted.borrow_mut().extend_from_slice(groups);
}
fn on_sticky_notes_delete(&mut self, notes: &[StickyNoteId]) {
self.sticky_notes_deleted
.borrow_mut()
.extend_from_slice(notes);
}
fn on_delete(&mut self, change: DeleteChange) {
self.deleted.borrow_mut().push(change);
}
fn on_disconnect(&mut self, conn: EdgeConnection) {
self.disconnected.borrow_mut().push(conn.edge);
}
}
impl NodeGraphViewCallbacks for Recorder {}
impl NodeGraphGestureCallbacks for Recorder {}
let (mut g0, a, _b, _out_port, _in_port, eid) = make_graph();
let group_id = GroupId::new();
let sticky_note_id = StickyNoteId::new();
let rect = CanvasRect {
origin: CanvasPoint { x: 0.0, y: 0.0 },
size: CanvasSize {
width: 10.0,
height: 10.0,
},
};
let group = Group {
title: "delete group".to_owned(),
rect,
color: None,
};
let sticky_note = StickyNote {
text: "delete note".to_owned(),
rect,
color: None,
};
g0.groups.insert(group_id, group.clone());
g0.sticky_notes.insert(sticky_note_id, sticky_note.clone());
let mut store = make_store(g0);
let nodes_deleted: Rc<RefCell<Vec<NodeId>>> = Rc::new(RefCell::new(Vec::new()));
let edges_deleted: Rc<RefCell<Vec<EdgeId>>> = Rc::new(RefCell::new(Vec::new()));
let groups_deleted: Rc<RefCell<Vec<GroupId>>> = Rc::new(RefCell::new(Vec::new()));
let sticky_notes_deleted: Rc<RefCell<Vec<StickyNoteId>>> = Rc::new(RefCell::new(Vec::new()));
let deleted: Rc<RefCell<Vec<DeleteChange>>> = Rc::new(RefCell::new(Vec::new()));
let disconnected: Rc<RefCell<Vec<EdgeId>>> = Rc::new(RefCell::new(Vec::new()));
let _token = install_callbacks(
&mut store,
Recorder {
nodes_deleted: nodes_deleted.clone(),
edges_deleted: edges_deleted.clone(),
groups_deleted: groups_deleted.clone(),
sticky_notes_deleted: sticky_notes_deleted.clone(),
deleted: deleted.clone(),
disconnected: disconnected.clone(),
},
);
let op = store
.graph()
.build_remove_node_op(a)
.expect("remove node op");
let tx = GraphTransaction::from_ops([
op,
GraphOp::RemoveGroup {
id: group_id,
group,
detached: Vec::new(),
bindings: Vec::new(),
},
GraphOp::RemoveStickyNote {
id: sticky_note_id,
note: sticky_note,
bindings: Vec::new(),
},
]);
let _ = store.dispatch_transaction(&tx).expect("dispatch remove");
assert_eq!(nodes_deleted.borrow().as_slice(), &[a]);
assert_eq!(edges_deleted.borrow().as_slice(), &[eid]);
assert_eq!(groups_deleted.borrow().as_slice(), &[group_id]);
assert_eq!(sticky_notes_deleted.borrow().as_slice(), &[sticky_note_id]);
assert_eq!(
deleted.borrow().as_slice(),
&[DeleteChange::from_parts(
vec![a],
vec![eid],
vec![group_id],
vec![sticky_note_id],
)],
);
assert_eq!(disconnected.borrow().as_slice(), &[eid]);
}