use super::*;
#[test]
fn store_does_not_commit_rejected_profile_edits() {
use crate::rules::{ConnectPlan, Diagnostic, DiagnosticSeverity, DiagnosticTarget};
use jellyflow_core::types::TypeDesc;
struct RejectProfile;
impl crate::profile::GraphProfile for RejectProfile {
fn type_of_port(
&mut self,
_graph: &Graph,
_port: jellyflow_core::core::PortId,
) -> Option<TypeDesc> {
None
}
fn plan_connect(
&mut self,
_graph: &Graph,
_a: jellyflow_core::core::PortId,
_b: jellyflow_core::core::PortId,
_mode: jellyflow_core::interaction::NodeGraphConnectionMode,
) -> ConnectPlan {
ConnectPlan::reject("not used in this test")
}
fn validate_graph(&mut self, _graph: &Graph) -> Vec<Diagnostic> {
vec![Diagnostic {
key: "test.reject".to_string(),
severity: DiagnosticSeverity::Error,
target: DiagnosticTarget::Graph,
message: "rejected by test profile".to_string(),
fixes: Vec::new(),
}]
}
}
let (g0, a, _b, _out_port, _in_port, _eid) = make_graph();
let mut store = NodeGraphStore::with_profile(
g0.clone(),
NodeGraphViewState::default(),
default_editor_config(),
Box::new(RejectProfile),
);
let tx = GraphTransaction::from_ops([GraphOp::SetNodePos {
id: a,
from: CanvasPoint { x: 0.0, y: 0.0 },
to: CanvasPoint { x: 999.0, y: 999.0 },
}]);
let err = store.dispatch_transaction(&tx).expect_err("reject");
let crate::runtime::store::DispatchError::Apply(crate::profile::ApplyPipelineError::Rejected {
diagnostics,
..
}) = err
else {
panic!("unexpected error: {err:?}");
};
assert!(!diagnostics.is_empty());
assert_eq!(
store.graph().nodes().get(&a).unwrap().pos,
g0.nodes().get(&a).unwrap().pos
);
assert!(!store.can_undo());
}
#[test]
fn store_rejects_non_finite_transactions() {
let g = Graph::new(jellyflow_core::core::GraphId::from_u128(1));
let node_id = NodeId::new();
let tx = GraphTransaction::from_ops([GraphOp::AddNode {
id: node_id,
node: Node {
kind: NodeKindKey::new("demo.node"),
kind_version: 1,
pos: CanvasPoint {
x: f32::NAN,
y: 0.0,
},
origin: None,
selectable: None,
focusable: None,
draggable: None,
connectable: None,
deletable: None,
parent: None,
extent: None,
expand_parent: None,
size: Some(jellyflow_core::core::CanvasSize {
width: 10.0,
height: 10.0,
}),
hidden: false,
collapsed: false,
ports: Vec::new(),
data: serde_json::Value::Null,
},
}]);
let mut store = make_store(g.clone());
let err = store.dispatch_transaction(&tx).expect_err("reject");
let crate::runtime::store::DispatchError::Apply(crate::profile::ApplyPipelineError::Rejected {
diagnostics,
..
}) = err
else {
panic!("unexpected error: {err:?}");
};
assert_eq!(diagnostics[0].key, "tx.non_finite");
assert!(store.graph().nodes().is_empty());
assert_eq!(store.graph().graph_id(), g.graph_id());
assert!(!store.can_undo());
}
#[test]
fn store_rejects_non_finite_edge_width_transactions() {
let (g, _a, _b, out_port, in_port, edge_id) = make_graph();
assert_rejects_non_finite_edge_tx(
g.clone(),
GraphTransaction::from_ops([GraphOp::SetEdgeInteractionWidth {
id: edge_id,
from: None,
to: Some(f32::INFINITY),
}]),
);
assert_rejects_non_finite_edge_tx(
g.clone(),
GraphTransaction::from_ops([GraphOp::SetEdgeView {
id: edge_id,
from: EdgeViewDescriptor::default(),
to: EdgeViewDescriptor::new().with_hit_target_width(f32::NAN),
}]),
);
let mut edge = Edge::new(EdgeKind::Data, out_port, in_port);
edge.interaction_width = Some(f32::INFINITY);
assert_rejects_non_finite_edge_tx(
g,
GraphTransaction::from_ops([GraphOp::AddEdge {
id: jellyflow_core::core::EdgeId::new(),
edge,
}]),
);
}
#[test]
fn store_rejects_invalid_size_transactions() {
let g = Graph::new(jellyflow_core::core::GraphId::from_u128(1));
let node_id = NodeId::new();
let tx = GraphTransaction::from_ops([GraphOp::AddNode {
id: node_id,
node: Node {
kind: NodeKindKey::new("demo.node"),
kind_version: 1,
pos: CanvasPoint { x: 0.0, y: 0.0 },
origin: None,
selectable: None,
focusable: None,
draggable: None,
connectable: None,
deletable: None,
parent: None,
extent: None,
expand_parent: None,
size: Some(jellyflow_core::core::CanvasSize {
width: 0.0,
height: 10.0,
}),
hidden: false,
collapsed: false,
ports: Vec::new(),
data: serde_json::Value::Null,
},
}]);
let mut store = make_store(g.clone());
let err = store.dispatch_transaction(&tx).expect_err("reject");
let crate::runtime::store::DispatchError::Apply(crate::profile::ApplyPipelineError::Rejected {
diagnostics,
..
}) = err
else {
panic!("unexpected error: {err:?}");
};
assert_eq!(diagnostics[0].key, "tx.invalid_size");
assert!(store.graph().nodes().is_empty());
assert_eq!(store.graph().graph_id(), g.graph_id());
assert!(!store.can_undo());
}
fn assert_rejects_non_finite_edge_tx(g: Graph, tx: GraphTransaction) {
let mut store = make_store(g.clone());
let err = store.dispatch_transaction(&tx).expect_err("reject");
let crate::runtime::store::DispatchError::Apply(crate::profile::ApplyPipelineError::Rejected {
diagnostics,
..
}) = err
else {
panic!("unexpected error: {err:?}");
};
assert_eq!(diagnostics[0].key, "tx.non_finite");
assert_eq!(
serde_json::to_value(store.graph()).expect("store graph serializes"),
serde_json::to_value(&g).expect("fixture graph serializes")
);
assert!(!store.can_undo());
}