use jellyflow_core::core::{
CanvasPoint, CanvasRect, CanvasSize, EdgeId, EdgeKind, EdgeLabelAnchor, EdgeRouteKind,
EdgeViewDescriptor, Graph, GraphId, GroupId, NodeId, PortDirection, PortId,
};
use jellyflow_core::interaction::NodeGraphConnectionMode;
use jellyflow_core::ops::{GraphMutationFootprint, GraphTransaction};
use jellyflow_runtime::io::{
GraphFileV1, NodeGraphEditorConfig, NodeGraphEditorStateFile, NodeGraphInteractionConfig,
NodeGraphInteractionState, NodeGraphPanInertiaTuning, NodeGraphPanOnDragButtons,
NodeGraphViewState,
};
use jellyflow_runtime::profile::{
ApplyPipelineError, ConnectionRuleDescriptor, FieldSchema, GraphProfile as ModuleGraphProfile,
GraphProfileMetadata, NodeFieldSchemaSet, ValidationHint, VariableDescriptor,
VariableSurfaceDescriptor,
};
use jellyflow_runtime::rules::{ConnectPlan, Diagnostic, DiagnosticTarget, EdgeEndpoint};
use jellyflow_runtime::runtime::{
auto_pan, chrome, commit, conformance, connection, create_node, delete, drag, events, geometry,
gesture, keyboard, layout, measurement, rendering, resize, selection, store, viewport, xyflow,
};
use jellyflow_runtime::schema::{
ActionIntent, ActionShortcut, ActionTarget, BlackboardDescriptor, InspectorDescriptor,
InspectorTarget, MenuDescriptor, MenuSurface, NodeActionDescriptor, NodeChromeDescriptor,
NodeChromeKind, NodeChromePlacement, NodeChromeVisibility, NodeControlBinding,
NodeControlDescriptor, NodeControlEditability, NodeControlKind, NodeControlOption,
NodeControlOptionSource, NodeControlPresentation, NodeControlValidation,
NodeControlValidationRule, NodeInstantiation, NodeInstantiationError, NodeKindViewDescriptor,
NodeKitContentDensity, NodeRegistry, NodeRepeatableAnchorRule,
NodeRepeatableCollectionDescriptor, NodeSchema, NodeSchemaBuilder, NodeSurfaceLayoutBudget,
NodeSurfaceOverflowIndicator, NodeSurfaceSlotDescriptor, NodeSurfaceSlotKind,
NodeSurfaceSlotVisibility, PortDecl, PortHandleVisibility, PortViewDescriptor, PortViewSide,
};
use jellyflow_runtime::{
DispatchError, DispatchOutcome, GraphProfile, NodeGraphPatch, NodeGraphStore,
apply_connect_plan_with_profile, apply_transaction_with_profile,
};
fn accepts_root_profile<T: ?Sized + GraphProfile>() {}
fn accepts_module_profile<T: ?Sized + ModuleGraphProfile>() {}
#[test]
fn crate_root_exposes_canonical_runtime_api() {
accepts_root_profile::<dyn GraphProfile>();
accepts_module_profile::<dyn ModuleGraphProfile>();
let _: fn(
&mut Graph,
&mut dyn GraphProfile,
&GraphTransaction,
) -> Result<GraphTransaction, ApplyPipelineError> = apply_transaction_with_profile;
let _: fn(
&mut Graph,
&mut dyn GraphProfile,
&ConnectPlan,
) -> Result<GraphTransaction, ApplyPipelineError> = apply_connect_plan_with_profile;
let graph = Graph::new(GraphId::new());
let store = NodeGraphStore::new(
graph.clone(),
NodeGraphViewState::default(),
NodeGraphEditorConfig::default(),
);
assert_eq!(store.graph().graph_id(), graph.graph_id());
assert!(NodeGraphPatch::default().is_empty());
let _: &GraphMutationFootprint = NodeGraphPatch::default().footprint();
let _ = std::mem::size_of::<DispatchOutcome>();
let _ = std::mem::size_of::<DispatchError>();
let dispatch = DispatchOutcome::from_committed(GraphTransaction::default());
let _: &NodeGraphPatch = dispatch.patch();
let _: NodeGraphPatch = dispatch.into_patch();
}
#[test]
fn explicit_modules_expose_their_owned_surfaces() {
let graph = Graph::new(GraphId::new());
let edge_view = EdgeViewDescriptor {
renderer_key: Some("public.edge".to_owned()),
label: Some("Public edge".to_owned()),
label_anchor: Some(EdgeLabelAnchor::Center),
source_marker_key: None,
target_marker_key: Some("arrow".to_owned()),
style_token: Some("accent".to_owned()),
route_kind: Some(EdgeRouteKind::SmoothStep),
hit_target_width: Some(24.0),
};
assert_eq!(edge_view.label_anchor, Some(EdgeLabelAnchor::Center));
assert_eq!(edge_view.route_kind, Some(EdgeRouteKind::SmoothStep));
let profile_metadata = GraphProfileMetadata::new("public.workflow", "Public workflow")
.with_node_fields(
NodeFieldSchemaSet::new("public.note").with_field(
FieldSchema::new("prompt", "Prompt")
.with_type(jellyflow_core::types::TypeDesc::String)
.required()
.with_hint(ValidationHint::new(
"public.prompt_required",
"Prompt is required",
))
.with_port_anchor("source"),
),
)
.with_variable_surface(
VariableSurfaceDescriptor::new("inputs", "Inputs").with_variable(
VariableDescriptor::new("topic", "Topic")
.with_type(jellyflow_core::types::TypeDesc::String),
),
)
.with_connection_rule(
ConnectionRuleDescriptor::new("public.exec_dag", "Execution flow must be acyclic")
.for_edge_kind(EdgeKind::Exec),
);
assert_eq!(profile_metadata.key.as_deref(), Some("public.workflow"));
assert_eq!(profile_metadata.node_fields[0].fields[0].key, "prompt");
assert_eq!(
profile_metadata.node_fields[0].fields[0]
.port_anchor
.as_ref()
.map(|key| key.0.as_str()),
Some("source")
);
let profile_diagnostic = Diagnostic::error(
"public.edge.invalid",
DiagnosticTarget::Edge { id: EdgeId::new() },
"edge is invalid",
);
assert_eq!(profile_diagnostic.key, "public.edge.invalid");
let graph_file = GraphFileV1::from_graph(graph.clone());
assert_eq!(graph_file.graph_id, graph.graph_id());
let editor_file = NodeGraphEditorStateFile::new(
graph.graph_id(),
NodeGraphViewState::default(),
NodeGraphEditorConfig::default(),
);
assert_eq!(editor_file.graph_id, graph.graph_id());
let _interaction = NodeGraphInteractionConfig::default();
let mut node_registry = NodeRegistry::new();
let _: NodeSchemaBuilder = NodeSchema::builder("public.note", "Note");
node_registry.register(
NodeSchema::builder("public.note", "Note")
.default_size(CanvasSize {
width: 120.0,
height: 80.0,
})
.layout_budget(
NodeSurfaceLayoutBudget::default()
.with_min_readable_size(CanvasSize {
width: 220.0,
height: 160.0,
})
.with_preferred_size(CanvasSize {
width: 260.0,
height: 190.0,
})
.with_slot_line_budget(3)
.with_control_line_budget(2)
.with_repeatable_visible_items(4)
.with_overflow_indicator(NodeSurfaceOverflowIndicator::Count)
.with_density_priority([
NodeKitContentDensity::Full,
NodeKitContentDensity::Regular,
NodeKitContentDensity::Compact,
]),
)
.port(
PortDecl::data_input("source")
.with_label("Source")
.with_view(
PortViewDescriptor::left()
.with_order(1)
.with_group("inputs")
.with_anchor("field.source")
.with_icon_key("file-text")
.with_visibility(PortHandleVisibility::Visible),
),
)
.surface_slot(
NodeSurfaceSlotDescriptor::field_row("field.source")
.with_label("Source")
.with_anchor("field.source")
.with_lane("fields")
.with_visibility(NodeSurfaceSlotVisibility::Visible)
.with_control(
NodeControlDescriptor::select("control.source_kind")
.with_label("Source kind")
.with_binding(NodeControlBinding::slot("source"))
.with_options([
NodeControlOption::new("file", "File"),
NodeControlOption::new("url", "URL"),
])
.with_validation(NodeControlValidation::default().required().with_rule(
NodeControlValidationRule::EnumValues {
values: vec![
serde_json::json!("file"),
serde_json::json!("url"),
],
},
))
.with_presentation(
NodeControlPresentation::default()
.with_placeholder("Choose a source")
.with_icon_key("file-text"),
)
.with_editability(
NodeControlEditability::default()
.disabled("Source is locked in this public API smoke"),
),
),
)
.repeatable_collection(
NodeRepeatableCollectionDescriptor::new("fields.args", "args", "id")
.with_label("Arguments")
.with_item_template_slot(
NodeSurfaceSlotDescriptor::field_row("field.arg")
.with_label("Argument")
.with_slot("args")
.with_control(
NodeControlDescriptor::text_input("control.arg.name")
.with_label("Name")
.with_binding(NodeControlBinding::data_path("name")),
),
)
.with_anchor_rule(
NodeRepeatableAnchorRule::new("field.arg", "field.arg")
.with_port_key_path("port"),
)
.with_min_items(0)
.with_max_items(4)
.reorderable()
.with_add_action("action.arg.add")
.with_remove_action("action.arg.remove")
.with_reorder_action("action.arg.reorder"),
)
.action(
NodeActionDescriptor::new(
"action.arg.add",
"Add argument",
ActionTarget::Node {
node_kind: "public.note".to_owned(),
},
ActionIntent::AddRepeatableItem {
collection_key: "fields.args".to_owned(),
},
)
.with_shortcut(ActionShortcut::new("A").ctrl()),
)
.menu(
MenuDescriptor::new("menu.node.public_note", MenuSurface::Node)
.with_action_key("action.arg.add"),
)
.inspector(
InspectorDescriptor::new(
"inspector.arg.topic",
InspectorTarget::RepeatableItem {
collection_key: "fields.args".to_owned(),
item_id: "topic".to_owned(),
},
)
.with_control(
NodeControlDescriptor::text_input("inspector.arg.name")
.with_label("Name")
.with_binding(NodeControlBinding::data_path("name")),
),
)
.blackboard(BlackboardDescriptor::new(
"blackboard.public.variables",
"Variables",
NodeRepeatableCollectionDescriptor::new("variables", "variables", "id"),
))
.chrome(
NodeChromeDescriptor::toolbar("toolbar.primary", NodeChromePlacement::TopRight)
.with_label("Tools")
.with_visibility(NodeChromeVisibility::Selected),
)
.build(),
);
let view_descriptors = node_registry.view_descriptors();
let _: &NodeKindViewDescriptor = &view_descriptors[0];
assert_eq!(view_descriptors[0].renderer_key, "public.note");
assert_eq!(
view_descriptors[0].layout_budget.min_readable_size,
Some(CanvasSize {
width: 220.0,
height: 160.0,
})
);
assert_eq!(
view_descriptors[0].layout_budget.overflow_indicator,
Some(NodeSurfaceOverflowIndicator::Count)
);
assert_eq!(
view_descriptors[0]
.layout_budget
.density_priority
.as_slice(),
&[
NodeKitContentDensity::Full,
NodeKitContentDensity::Regular,
NodeKitContentDensity::Compact,
]
);
assert_eq!(
view_descriptors[0].surface_slots[0].kind,
NodeSurfaceSlotKind::FieldRow
);
assert_eq!(
view_descriptors[0].surface_slots[0].controls[0].kind,
NodeControlKind::Select
);
assert_eq!(
view_descriptors[0].surface_slots[0].controls[0].data_key(),
Some("source")
);
assert_eq!(
view_descriptors[0].surface_slots[0].controls[0].option_source,
NodeControlOptionSource::Inline
);
assert_eq!(
view_descriptors[0].surface_slots[0].controls[0]
.editability
.disabled_reason
.as_deref(),
Some("Source is locked in this public API smoke")
);
let projected_slots = view_descriptors[0].surface_slots_projection(
&serde_json::json!({ "source": "file" }),
None,
1.0,
);
assert_eq!(
projected_slots[0].controls[0].label.as_deref(),
Some("Source kind")
);
let args = view_descriptors[0].repeatable_items_projection(
&serde_json::json!({
"source": "file",
"args": [
{ "id": "topic", "name": "Topic", "port": "arg_topic" },
{ "id": "tone", "name": "Tone", "port": "arg_tone" }
]
}),
"fields.args",
);
assert_eq!(args[0].slot_key, "field.arg.topic");
assert_eq!(args[0].anchor, "field.arg.topic");
assert_eq!(args[0].port_key.as_deref(), Some("arg_topic"));
assert_eq!(args[1].item_id, "tone");
assert!(
view_descriptors[0]
.action("action.arg.add")
.expect("action")
.is_enabled()
);
assert_eq!(
view_descriptors[0]
.menu("menu.node.public_note")
.expect("menu")
.action_keys,
vec!["action.arg.add"]
);
assert_eq!(
view_descriptors[0]
.inspector("inspector.arg.topic")
.expect("inspector")
.controls[0]
.data_key(),
Some("name")
);
assert_eq!(
view_descriptors[0]
.blackboard("blackboard.public.variables")
.expect("blackboard")
.collection
.key,
"variables"
);
assert_eq!(
view_descriptors[0].ports[0].label.as_deref(),
Some("Source")
);
assert_eq!(
view_descriptors[0].ports[0].view.side,
Some(PortViewSide::Left)
);
assert_eq!(view_descriptors[0].ports[0].view.order, Some(1));
assert_eq!(
view_descriptors[0].ports[0].view.anchor.as_deref(),
Some("field.source")
);
assert_eq!(view_descriptors[0].chrome[0].kind, NodeChromeKind::Toolbar);
assert_eq!(
view_descriptors[0].chrome[0].effective_visibility(),
NodeChromeVisibility::Selected
);
let chrome_facts = chrome::resolve_node_chrome_facts(
chrome::NodeChromeFactsRequest::new(
NodeId::new(),
CanvasRect {
origin: CanvasPoint::default(),
size: CanvasSize {
width: 120.0,
height: 80.0,
},
},
&view_descriptors[0].chrome,
)
.with_state(chrome::NodeChromeState::selected()),
)
.expect("public chrome facts");
let _: &chrome::NodeChromeFacts = &chrome_facts;
let _: &chrome::ResolvedNodeChrome = &chrome_facts.chrome[0];
let _: chrome::NodeChromeLayoutPolicy = chrome::NodeChromeLayoutPolicy::default();
assert!(
node_registry
.view_descriptor(&jellyflow_core::core::NodeKindKey::new("public.note"))
.is_some()
);
let node_instantiation = node_registry
.instantiate_node(
&jellyflow_core::core::NodeKindKey::new("public.note"),
CanvasPoint::default(),
)
.expect("public node instantiation");
let _: &NodeInstantiation = &node_instantiation;
assert_eq!(
node_instantiation.node.kind,
jellyflow_core::core::NodeKindKey::new("public.note")
);
assert_eq!(
NodeInstantiationError::MissingSchema(jellyflow_core::core::NodeKindKey::new(
"public.missing"
))
.to_string(),
"node kind schema not found: NodeKindKey(\"public.missing\")"
);
let _: fn(jellyflow_core::core::NodeKindKey, CanvasPoint) -> create_node::CreateNodeRequest =
create_node::CreateNodeRequest::new;
let create_node_request = create_node::CreateNodeRequest::new(
jellyflow_core::core::NodeKindKey::new("public.note"),
CanvasPoint::default(),
);
let mut create_node_store = NodeGraphStore::new(
graph.clone(),
NodeGraphViewState::default(),
NodeGraphEditorConfig::default(),
);
let create_node_outcome = create_node_store
.apply_create_node_from_schema(&node_registry, create_node_request.clone());
assert!(create_node_outcome.is_ok());
let _: fn(
&NodeGraphStore,
&NodeRegistry,
create_node::CreateNodeRequest,
) -> Result<NodeInstantiation, NodeInstantiationError> =
NodeGraphStore::plan_create_node_from_schema;
let _: fn(
&mut NodeGraphStore,
&NodeRegistry,
create_node::CreateNodeRequest,
) -> Result<create_node::CreateNodeOutcome, create_node::CreateNodeError> =
NodeGraphStore::apply_create_node_from_schema;
assert_eq!(
create_node_request.kind,
jellyflow_core::core::NodeKindKey::new("public.note")
);
assert_eq!(create_node::CREATE_NODE_TRANSACTION_LABEL, "create node");
let root_patch = NodeGraphPatch::default();
let module_patch = commit::NodeGraphPatch::default();
assert!(root_patch.is_empty());
assert!(module_patch.is_empty());
let selection_result = selection::SelectionBoxResult::default();
assert!(selection_result.is_empty());
let _selection_options = selection::SelectionBoxOptions::default();
let selection_input = selection::SelectionBoxInput::replace(CanvasRect {
origin: CanvasPoint::default(),
size: CanvasSize {
width: 10.0,
height: 10.0,
},
});
let reverse_drag_selection_input = selection::SelectionBoxInput::replace_from_drag(
CanvasPoint { x: 10.0, y: 10.0 },
CanvasPoint::default(),
);
assert_eq!(reverse_drag_selection_input.rect, selection_input.rect);
let selection_store = NodeGraphStore::new(
graph.clone(),
NodeGraphViewState::default(),
NodeGraphEditorConfig::default(),
);
let selection_decision = selection::resolve_selection_box(
&graph,
selection_store.lookups(),
selection_store.view_state(),
&NodeGraphInteractionState::default(),
selection_input,
);
assert!(selection_decision.result().is_empty());
let _selection_modifier = selection::SelectionModifier::Additive;
assert!(selection::selection_drag_threshold_met(
selection::SelectionDragActivationInput::new(CanvasPoint { x: 3.0, y: 4.0 }, 4.0, false),
));
let drag_start_selection = selection::resolve_node_drag_start_selection(
&graph,
&NodeGraphViewState::default(),
&NodeGraphInteractionState::default(),
selection::NodeDragStartSelectionInput::new(NodeId::new(), false),
);
assert_eq!(
drag_start_selection,
selection::NodeDragStartSelectionAction::Unchanged
);
let _drag_request = drag::NodeDragRequest {
node: NodeId::new(),
to: CanvasPoint::default(),
};
let _layout_request = layout::LayoutRequest::all();
let _layout_context = layout::LayoutContext::new();
let layout_engine_request = layout::LayoutEngineRequest::dugong(layout::LayoutRequest::all());
let preset_layout_engine_request = layout::LayoutPresetBuilder::tree().build();
let layout_registry = layout::builtin_layout_engine_registry();
assert!(
layout_registry
.get(&layout::LayoutEngineId::dugong())
.is_some()
);
assert!(
layout_registry
.get(&layout::LayoutEngineId::tidy_tree())
.is_some()
);
assert!(
layout_registry
.get(&layout::LayoutEngineId::mind_map_radial())
.is_some()
);
assert!(
layout_registry
.get(&layout::LayoutEngineId::mind_map_freeform())
.is_some()
);
assert!(
layout_registry
.family(&layout::LayoutFamilyId::mind_map())
.is_some()
);
assert!(
layout_registry
.metadata(&layout::LayoutEngineId::mind_map_radial())
.is_some()
);
assert_eq!(
layout_registry
.engines_for_family(&layout::LayoutFamilyId::layered_dag())
.count(),
2
);
assert_eq!(
layout_registry
.engines_for_family(&layout::LayoutFamilyId::mind_map())
.count(),
2
);
let _ = layout::DUGONG_LAYOUT_ENGINE_ID;
let _ = layout::TIDY_TREE_LAYOUT_ENGINE_ID;
let _ = layout::MIND_MAP_RADIAL_LAYOUT_ENGINE_ID;
let _ = layout::MIND_MAP_FREEFORM_LAYOUT_ENGINE_ID;
let _ = layout::LAYERED_DAG_LAYOUT_FAMILY_ID;
let _ = layout::MIND_MAP_LAYOUT_FAMILY_ID;
let _ = std::mem::size_of::<layout::LayoutFamilyId>();
let _ = std::mem::size_of::<layout::LayoutFamilyMetadata>();
let _ = std::mem::size_of::<layout::LayoutEngineMetadata>();
let _ = std::mem::size_of::<layout::LayoutEngineCapability>();
let _ = std::mem::size_of::<layout::LayoutPresetBuilder>();
let _ = std::mem::size_of::<layout::DugongLayoutEngine>();
let _ = std::mem::size_of::<layout::TidyTreeLayoutEngine>();
let _ = std::mem::size_of::<layout::MindMapRadialLayoutEngine>();
let _ = std::mem::size_of::<layout::MindMapFreeformLayoutEngine>();
let _ = std::mem::size_of::<layout::LayoutApplyOutcome>();
let _ = std::mem::size_of::<layout::LayoutApplyError>();
let _ = std::mem::size_of::<layout::DugongLayoutApplyOutcome>();
let _ = std::mem::size_of::<layout::DugongLayoutApplyError>();
let _: fn(&NodeGraphStore) -> layout::LayoutContext = layout::layout_context_from_store;
let _: fn(&NodeGraphStore) -> layout::LayoutContext =
NodeGraphStore::layout_context_with_binding_pins;
let _: fn(
&Graph,
&layout::LayoutEngineRequest,
&layout::LayoutEngineRegistry,
&layout::LayoutContext,
) -> Result<layout::LayoutResult, layout::LayoutError> = layout::plan_layout;
let _: fn(
&Graph,
&layout::LayoutEngineRequest,
&layout::LayoutEngineRegistry,
&layout::LayoutContext,
) -> Result<GraphTransaction, layout::LayoutError> = layout::layout_transaction;
let _ = layout_engine_request;
let _ = preset_layout_engine_request;
let _binding_query = selection_store.binding_query();
let _binding_query_with_options = selection_store.binding_query_with_options(
jellyflow_runtime::runtime::binding::BindingQueryOptions::default()
.include_hidden(true)
.with_fallback_node_size(Some(CanvasSize {
width: 10.0,
height: 10.0,
})),
);
let _ = std::mem::size_of::<jellyflow_runtime::runtime::binding::BindingQueryResult>();
let _ = std::mem::size_of::<jellyflow_runtime::runtime::binding::ResolvedBinding>();
let _ = std::mem::size_of::<jellyflow_runtime::runtime::binding::ResolvedBindingEndpoint>();
let _ = std::mem::size_of::<jellyflow_runtime::runtime::binding::BindingEndpointResolution>();
let _ =
std::mem::size_of::<jellyflow_runtime::runtime::binding::BindingEndpointResolutionStatus>();
assert!(connection::connection_drag_threshold_met(
connection::ConnectionDragActivationInput::new(CanvasPoint { x: 3.0, y: 4.0 }, 4.0),
));
let from_handle = connection::ConnectionHandleRef::new(
NodeId::new(),
jellyflow_core::core::PortId::new(),
jellyflow_core::core::PortDirection::Out,
);
let _: Option<connection::ClosestConnectionHandle> =
connection::closest_connection_handle(connection::ClosestConnectionHandleInput::new(
CanvasPoint::default(),
0.0,
from_handle,
&[],
));
assert_eq!(
connection::connection_handle_validity(true, false),
connection::ConnectionHandleValidity::Invalid
);
let target_handle = connection::ConnectionTargetHandle::new(
connection::ConnectionHandleRef::new(
NodeId::new(),
jellyflow_core::core::PortId::new(),
jellyflow_core::core::PortDirection::In,
),
true,
true,
);
let resolved_target =
connection::resolve_connection_target(connection::ConnectionTargetInput::new(
from_handle,
Some(target_handle),
NodeGraphConnectionMode::Strict,
true,
));
let target_candidate = connection::ConnectionTargetCandidate::new(
target_handle,
CanvasRect {
origin: CanvasPoint::default(),
size: CanvasSize {
width: 100.0,
height: 80.0,
},
},
geometry::HandleBounds {
rect: CanvasRect {
origin: CanvasPoint::default(),
size: CanvasSize {
width: 10.0,
height: 10.0,
},
},
position: geometry::HandlePosition::Right,
},
);
let target_candidates = [target_candidate];
let resolved_target_from_handles = connection::resolve_connection_target_from_handles(
connection::ConnectionTargetFromHandlesInput::new(
CanvasPoint { x: 5.0, y: 5.0 },
10.0,
from_handle,
&target_candidates,
NodeGraphConnectionMode::Strict,
),
);
let _: Option<connection::ConnectionHandleConnection> = resolved_target.connection;
let _: Option<connection::ConnectionHandleConnection> = resolved_target_from_handles.connection;
assert_eq!(
resolved_target.feedback,
connection::ConnectionHandleValidity::Valid
);
let indicator = connection::resolve_connection_handle_indicator(
connection::ConnectionHandleIndicatorInput::new(
target_handle.handle,
NodeGraphConnectionMode::Strict,
)
.with_connection(
Some(from_handle),
Some(target_handle.handle),
resolved_target.feedback,
),
);
let _: connection::ConnectionHandleIndicator = indicator;
assert!(indicator.show_connection_indicator);
let connection_start = connection::new_connection_start(
from_handle.port,
[from_handle.port],
NodeGraphConnectionMode::Strict,
);
let lifecycle = connection::resolve_connection_lifecycle(
connection_start.clone(),
Some(resolved_target),
connection::ConnectionEndIntent::Complete,
);
assert_eq!(
lifecycle.state,
connection::ConnectionLifecycleState::Committed
);
assert!(lifecycle.did_commit());
assert_eq!(lifecycle.start, connection_start);
let hover_lifecycle =
connection::ConnectionLifecycleResult::for_hover(connection_start, Some(resolved_target));
assert_eq!(
hover_lifecycle.state,
connection::ConnectionLifecycleState::HoverValid
);
let dropped_lifecycle = connection::resolve_connection_lifecycle(
connection::new_connection_start(
from_handle.port,
[from_handle.port],
NodeGraphConnectionMode::Strict,
),
None,
connection::ConnectionEndIntent::DropOnPane {
pointer: CanvasPoint { x: 2.0, y: 3.0 },
menu: None,
},
);
assert!(dropped_lifecycle.opens_dropped_wire_menu());
let _ = std::mem::size_of::<geometry::ResolvedEdgeRouteKind>();
let _ = std::mem::size_of::<geometry::EdgeInteractionFacts>();
let _ = std::mem::size_of::<geometry::EdgeRouteFacts>();
let _ = std::mem::size_of::<connection::ConnectionLifecycleResult>();
let _ = std::mem::size_of::<connection::ConnectEdgeRequest>();
let _ = std::mem::size_of::<connection::ConnectEdgeError>();
let _: fn(&ConnectPlan) -> Option<GraphTransaction> = connection::connect_edge_transaction;
let _: fn(&ConnectPlan, EdgeId) -> Option<GraphTransaction> =
connection::connect_edge_transaction_with_edge_id;
assert_eq!(connection::CONNECT_EDGE_TRANSACTION_LABEL, "connect edge");
let _ = std::mem::size_of::<connection::ReconnectEdgeRequest>();
let _ = std::mem::size_of::<connection::ReconnectEdgeError>();
let _: fn(&ConnectPlan) -> Option<GraphTransaction> = connection::reconnect_edge_transaction;
assert_eq!(
connection::RECONNECT_EDGE_TRANSACTION_LABEL,
"reconnect edge"
);
assert!(drag::node_drag_threshold_met(
drag::NodeDragActivationInput::new(CanvasPoint { x: 3.0, y: 4.0 }, 4.0),
));
assert_eq!(
drag::resolve_pointer_gesture_claim(drag::PointerGestureClaimInput::new(
CanvasPoint { x: 3.0, y: 4.0 },
false,
false,
false,
8.0,
4.0,
)),
drag::PointerGestureClaim::NodeDrag
);
let _drag_item = drag::NodeDragItem {
node: NodeId::new(),
from: CanvasPoint::default(),
to: CanvasPoint::default(),
};
let _node_drag_session = gesture::NodeDragSession::new(
NodeId::new(),
CanvasPoint::default(),
CanvasPoint { x: 8.0, y: 8.0 },
);
let _connection_handle_target = gesture::PointerSessionTarget::ConnectionHandle(from_handle);
let _ = std::mem::size_of::<gesture::ConnectEdgeSession>();
let _ = std::mem::size_of::<gesture::ConnectSessionOutcome>();
let _viewport_drag_pan_session = gesture::ViewportDragPanSession::new(
viewport::ViewportGestureContext::idle(),
viewport::ViewportDragPanInput::new(
viewport::ViewportPointerButton::Left,
CanvasPoint { x: 3.0, y: 4.0 },
),
);
assert_eq!(
gesture::PointerSessionClaim::NodeDrag,
gesture::PointerSessionClaim::NodeDrag
);
let _ = std::mem::size_of::<gesture::PointerSessionClaimOutcome>();
let _ = std::mem::size_of::<gesture::PointerSessionClaimRejection>();
let _: fn(
&NodeGraphStore,
gesture::PointerSessionClaimInput,
) -> gesture::PointerSessionClaimOutcome = NodeGraphStore::resolve_pointer_session_claim;
let measured_handle = measurement::MeasuredHandle::new(
from_handle,
geometry::HandleBounds {
rect: CanvasRect {
origin: CanvasPoint::default(),
size: CanvasSize {
width: 10.0,
height: 10.0,
},
},
position: geometry::HandlePosition::Right,
},
);
let measured_slot = measurement::MeasuredSurfaceSlot::new(
"field.prompt",
CanvasRect {
origin: CanvasPoint::default(),
size: CanvasSize {
width: 80.0,
height: 24.0,
},
},
);
let measured_anchor = measurement::MeasuredSurfaceAnchor::new(
"field.prompt.input",
CanvasRect {
origin: CanvasPoint::default(),
size: CanvasSize {
width: 10.0,
height: 10.0,
},
},
geometry::HandlePosition::Left,
)
.with_port_key("input");
let _node_measurement = measurement::NodeMeasurement::new(NodeId::new())
.with_revision(1)
.with_size(Some(CanvasSize {
width: 100.0,
height: 80.0,
}))
.with_handles([measured_handle])
.with_slots([measured_slot])
.with_anchors([measured_anchor]);
let _node_internals_invalidation = measurement::NodeInternalsInvalidation::one(
NodeId::new(),
measurement::NodeInternalsInvalidationReason::DataChanged,
);
assert!(measurement::NodeMeasurementStatus::Fresh { revision: 1 }.is_fresh());
assert!(
measurement::NodeMeasurementStatus::Dirty {
revision: 1,
reason: measurement::NodeInternalsInvalidationReason::AdapterRequest,
}
.is_dirty()
);
let _handle_resolution = measurement::NodeHandleMeasurementResolution {
handle: from_handle,
bounds: None,
source: measurement::NodeHandleMeasurementSource::Fallback {
reason: measurement::NodeHandleFallbackReason::MissingMeasurement,
},
status: measurement::NodeMeasurementStatus::Missing,
};
let _ = std::mem::size_of::<measurement::NodeMeasurementOutcome>();
let _ = std::mem::size_of::<measurement::NodeMeasurementError>();
let _ = std::mem::size_of::<measurement::LayoutEdgePosition>();
let _ = std::mem::size_of::<measurement::LayoutNodeMeasurementStatus>();
let _ = std::mem::size_of::<measurement::LayoutFactsQueryResult>();
let _: fn(
&mut NodeGraphStore,
measurement::NodeMeasurement,
)
-> Result<measurement::NodeMeasurementOutcome, measurement::NodeMeasurementError> =
NodeGraphStore::report_node_measurement;
let _: fn(&mut NodeGraphStore, NodeId) -> measurement::NodeMeasurementOutcome =
NodeGraphStore::clear_node_measurement;
let _: fn(
&mut NodeGraphStore,
measurement::NodeInternalsInvalidation,
) -> measurement::NodeMeasurementOutcome = NodeGraphStore::invalidate_node_internals;
let _: fn(&NodeGraphStore, NodeId) -> Option<measurement::NodeMeasurement> =
NodeGraphStore::node_measurement;
let _: fn(&NodeGraphStore, NodeId) -> measurement::NodeMeasurementStatus =
NodeGraphStore::node_measurement_status;
let _: fn(
&NodeGraphStore,
connection::ConnectionHandleRef,
) -> measurement::NodeHandleMeasurementResolution =
NodeGraphStore::resolve_node_handle_measurement;
let _: fn(&NodeGraphStore) -> u64 = NodeGraphStore::layout_facts_revision;
let _: fn(&NodeGraphStore, CanvasSize) -> measurement::LayoutFactsQueryResult =
NodeGraphStore::layout_facts_query;
let _: fn(&NodeGraphStore) -> Vec<connection::ConnectionTargetCandidate> =
NodeGraphStore::connection_target_candidates_from_layout_facts;
let _: fn(
&NodeGraphStore,
CanvasPoint,
connection::ConnectionHandleRef,
) -> connection::ResolvedConnectionTarget =
NodeGraphStore::resolve_connection_target_from_layout_facts;
let _: fn(&NodeGraphStore, EdgeId) -> Option<geometry::EdgePosition> =
NodeGraphStore::edge_position_from_layout_facts;
let _ = std::mem::size_of::<selection::NodePointerDownDecision>();
assert_eq!(drag::NODE_DRAG_TRANSACTION_LABEL, "node drag");
let _ = std::mem::size_of::<drag::NodeDragPlan>();
let resize_request = resize::NodeResizeRequest::new(
NodeId::new(),
CanvasSize {
width: 1.0,
height: 1.0,
},
)
.with_constraints(resize::NodeResizeConstraints::unconstrained());
let _: resize::NodeResizeRequest = resize_request;
let _: resize::NodeResizeRequest =
resize_request.with_direction(resize::NodeResizeDirection::BottomRight);
let pointer_resize_request = resize::NodePointerResizeRequest::new(
NodeId::new(),
CanvasPoint { x: 1.0, y: 1.0 },
CanvasPoint { x: 2.0, y: 3.0 },
resize::NodeResizeDirection::BottomRight,
)
.with_constraints(resize::NodeResizeConstraints::unconstrained())
.with_keep_aspect_ratio(true)
.with_axis(resize::NodeResizeAxis::Both);
let _: resize::NodePointerResizeRequest = pointer_resize_request;
let _: resize::NodeResizeContext = resize::NodeResizeContext::new((0.5, 0.5));
let _ = std::mem::size_of::<resize::NodeResizeAxis>();
let _ = std::mem::size_of::<resize::NodeResizeDirection>();
let _ = std::mem::size_of::<resize::NodeResizeItem>();
let _ = std::mem::size_of::<resize::NodeResizePlan>();
let _: fn(&Graph, resize::NodeResizeRequest) -> Option<resize::NodeResizePlan> =
resize::plan_node_resize;
let _: fn(
&Graph,
resize::NodeResizeContext,
resize::NodeResizeRequest,
) -> Option<resize::NodeResizePlan> = resize::plan_node_resize_with_context;
let _: fn(&Graph, resize::NodePointerResizeRequest) -> Option<resize::NodeResizePlan> =
resize::plan_node_pointer_resize;
let _: fn(
&Graph,
resize::NodeResizeContext,
resize::NodePointerResizeRequest,
) -> Option<resize::NodeResizePlan> = resize::plan_node_pointer_resize_with_context;
let _: fn(&NodeGraphStore, resize::NodePointerResizeRequest) -> Option<resize::NodeResizePlan> =
NodeGraphStore::plan_node_pointer_resize;
let _: fn(
&mut NodeGraphStore,
resize::NodePointerResizeRequest,
) -> Result<Option<DispatchOutcome>, DispatchError> = NodeGraphStore::apply_node_pointer_resize;
assert_eq!(resize::NODE_RESIZE_TRANSACTION_LABEL, "node resize");
assert_eq!(
delete::DELETE_SELECTION_TRANSACTION_LABEL,
"delete selection"
);
let delete_elements = delete::DeleteElements::new([NodeId::new()], [EdgeId::new()]);
let _pre_delete =
delete::PreDeleteRequest::new(delete_elements.clone(), delete_elements.clone());
let _pre_delete_accept = delete::PreDeleteResolution::accept();
let _pre_delete_veto = delete::PreDeleteResolution::veto();
let _pre_delete_replace =
delete::PreDeleteResolution::replace([NodeId::new()], [EdgeId::new()]);
let _: fn(&NodeGraphViewState) -> delete::DeleteElements = delete::delete_selection_elements;
let _: fn(&jellyflow_runtime::rules::DeletePlan) -> delete::DeleteElements =
delete::delete_elements_from_plan;
let _: fn(
&NodeGraphStore,
) -> Result<Option<delete::PreDeleteRequest>, delete::DeleteSelectionError> =
NodeGraphStore::prepare_delete_selection;
let _: fn(
&mut NodeGraphStore,
&delete::PreDeleteRequest,
delete::PreDeleteResolution,
) -> Result<Option<DispatchOutcome>, delete::DeleteSelectionError> =
NodeGraphStore::apply_pre_delete_resolution;
let delete_plan = delete::plan_delete_selection(
&graph,
&NodeGraphViewState::default(),
&NodeGraphInteractionState::default(),
);
assert!(delete::delete_selection_transaction(&delete_plan).is_none());
let _ = std::mem::size_of::<delete::DeleteSelectionError>();
let _ = std::mem::size_of::<keyboard::KeyboardIntent>();
let _ = std::mem::size_of::<keyboard::KeyboardActionError>();
let _ = std::mem::size_of::<keyboard::KeyboardActionOutcome>();
let _ = std::mem::size_of::<keyboard::KeyboardDeleteAction>();
let transform =
viewport::ViewportTransform::new(CanvasPoint { x: 1.0, y: 2.0 }, 1.5).expect("viewport");
assert_eq!(
viewport::resolve_pane_click_distance(viewport::PaneClickDistanceInput::new(3.0, false)),
3.0
);
let panned = viewport::pan_viewport(
transform,
viewport::ViewportPanRequest::new(CanvasPoint { x: 3.0, y: 6.0 }),
)
.expect("pan");
let zoomed = viewport::zoom_viewport(
panned,
viewport::ViewportZoomRequest::new(CanvasPoint { x: 24.0, y: 12.0 }, 2.0, 0.5, 4.0),
)
.expect("zoom");
let animation_plan =
viewport::plan_viewport_animation_with_options(viewport::ViewportAnimationRequest::new(
transform,
zoomed,
viewport::ViewportAnimationOptions::new(0.25)
.with_easing(viewport::ViewportAnimationEasing::Linear),
))
.expect("viewport animation plan");
let animation_frame = animation_plan
.frame_at(0.125)
.expect("viewport animation frame");
assert!(!animation_frame.done);
assert_eq!(
animation_plan.easing,
viewport::ViewportAnimationEasing::Linear
);
let _ = std::mem::size_of::<viewport::ViewportAnimationEasing>();
let _ = std::mem::size_of::<viewport::ViewportAnimationPlan>();
let _ = std::mem::size_of::<viewport::ViewportAnimationFrame>();
let inertia_plan =
viewport::plan_viewport_pan_inertia(viewport::ViewportPanInertiaRequest::new(
transform,
CanvasPoint { x: 500.0, y: 0.0 },
NodeGraphPanInertiaTuning {
enabled: true,
decay_per_s: 2.0,
min_speed: 100.0,
max_speed: 1000.0,
},
))
.expect("pan inertia plan");
let inertia_frame = inertia_plan.frame_at(0.25).expect("pan inertia frame");
assert!(!inertia_frame.done);
let _ = std::mem::size_of::<viewport::ViewportPanInertiaRequest>();
let _ = std::mem::size_of::<viewport::ViewportPanInertiaPlan>();
let _ = std::mem::size_of::<viewport::ViewportPanInertiaFrame>();
let interaction_state = NodeGraphInteractionState::default();
let double_click_plan = viewport::resolve_viewport_double_click_zoom(
&interaction_state.zoom_interaction(),
viewport::ViewportDoubleClickZoomInput::new(
transform,
CanvasPoint { x: 24.0, y: 12.0 },
2.0,
0.5,
4.0,
viewport::ViewportAnimationOptions::new(0.2),
),
)
.expect("double-click zoom policy");
assert_eq!(double_click_plan.from, transform);
let _ = std::mem::size_of::<viewport::ViewportDoubleClickZoomInput>();
let scroll_intent = viewport::resolve_viewport_scroll_gesture(
&interaction_state.pan_interaction(),
&interaction_state.zoom_interaction(),
viewport::ViewportGestureContext::idle(),
viewport::ViewportScrollInput::new(
CanvasPoint { x: 3.0, y: 6.0 },
CanvasPoint { x: 24.0, y: 12.0 },
false,
2.0,
0.5,
4.0,
),
)
.expect("scroll policy");
assert_eq!(
scroll_intent.move_kind(),
events::ViewportMoveKind::ZoomWheel
);
let drag_state = NodeGraphInteractionState {
pan_on_drag: NodeGraphPanOnDragButtons {
left: true,
middle: false,
right: false,
},
..NodeGraphInteractionState::default()
};
let drag_intent = viewport::resolve_viewport_drag_pan_gesture(
&drag_state.pan_interaction(),
viewport::ViewportGestureContext::idle(),
viewport::ViewportDragPanInput::new(
viewport::ViewportPointerButton::Left,
CanvasPoint { x: 1.0, y: 0.0 },
),
)
.expect("drag pan policy");
assert_eq!(drag_intent.move_kind(), events::ViewportMoveKind::PanDrag);
let _ = std::mem::size_of::<viewport::ViewportGestureRejection>();
let auto_pan_request = auto_pan::AutoPanRequest::new(
auto_pan::AutoPanActivation::Always,
CanvasPoint { x: 99.0, y: 40.0 },
jellyflow_core::core::CanvasSize {
width: 100.0,
height: 80.0,
},
0.016,
);
let auto_pan_plan = auto_pan::compute_auto_pan(
&jellyflow_runtime::io::NodeGraphAutoPanTuning::default(),
auto_pan_request,
)
.expect("auto-pan");
let selection_auto_pan_request = auto_pan::SelectionAutoPanRequest::new(
CanvasPoint { x: 99.0, y: 40.0 },
jellyflow_core::core::CanvasSize {
width: 100.0,
height: 80.0,
},
0.016,
);
let _ = auto_pan::compute_selection_auto_pan(
&jellyflow_runtime::io::NodeGraphAutoPanTuning::default(),
selection_auto_pan_request,
)
.expect("selection auto-pan");
let _ = auto_pan_plan.viewport_pan_request();
let _ = std::mem::size_of::<auto_pan::AutoPanOutcome>();
let _: fn(
&mut NodeGraphStore,
auto_pan::SelectionAutoPanRequest,
) -> Option<auto_pan::AutoPanOutcome> = NodeGraphStore::apply_selection_auto_pan;
let drag_start = events::NodeDragStart {
primary: NodeId::new(),
nodes: Vec::new(),
pointer: CanvasPoint::default(),
};
let drag_update = events::NodeDragUpdate {
primary: drag_start.primary,
nodes: drag_start.nodes.clone(),
pointer: drag_start.pointer,
};
let _drag_end = events::NodeDragEnd {
primary: drag_start.primary,
nodes: drag_start.nodes,
pointer: drag_update.pointer,
outcome: events::NodeDragEndOutcome::NoOp,
};
let _gesture = events::NodeGraphGestureEvent::NodeDragUpdate(drag_update);
let resize_start = events::NodeResizeStart {
node: NodeId::new(),
direction: resize::NodeResizeDirection::BottomRight,
pointer: CanvasPoint::default(),
};
let resize_update = events::NodeResizeUpdate {
node: resize_start.node,
direction: resize_start.direction,
pointer: CanvasPoint { x: 2.0, y: 3.0 },
position: CanvasPoint::default(),
size: CanvasSize {
width: 4.0,
height: 5.0,
},
};
let resize_end = events::NodeResizeEnd {
node: resize_start.node,
direction: resize_start.direction,
pointer: resize_update.pointer,
outcome: events::NodeResizeEndOutcome::Committed,
};
let _resize_start_event = events::NodeGraphGestureEvent::NodeResizeStart(resize_start.clone());
let _resize_update_event =
events::NodeGraphGestureEvent::NodeResizeUpdate(resize_update.clone());
let _resize_end_event = events::NodeGraphGestureEvent::NodeResizeEnd(resize_end.clone());
let mut module_store = store::NodeGraphStore::new(
graph.clone(),
NodeGraphViewState::default(),
NodeGraphEditorConfig::default(),
);
let _ = std::mem::size_of::<events::NodeGraphStoreEvent<'_>>();
let _ = std::mem::size_of::<events::NodeGraphStoreSnapshot<'_>>();
let _ = std::mem::size_of::<events::SubscriptionToken>();
let _revision_token = module_store.subscribe_selector(
|snapshot| (snapshot.graph_revision, snapshot.layout_facts_revision),
|_| {},
);
let _rendering_token =
module_store.subscribe_selector(|snapshot| snapshot.view_state.zoom.to_bits(), |_| {});
let _: fn(&NodeGraphStore) -> u64 = NodeGraphStore::graph_revision;
let _: fn(&NodeGraphStore) -> Vec<GroupId> = NodeGraphStore::group_render_order;
let _: fn(&NodeGraphStore) -> Vec<NodeId> = NodeGraphStore::node_render_order;
let _: fn(&NodeGraphStore) -> Vec<EdgeId> = NodeGraphStore::edge_render_order;
let _ = std::mem::size_of::<rendering::RenderingQueryResult>();
let query = module_store.rendering_query(CanvasSize {
width: 100.0,
height: 80.0,
});
assert!(query.group_order.is_empty());
assert!(query.node_order.is_empty());
assert!(query.edge_order.is_empty());
assert!(query.visible_node_ids.is_empty());
assert!(query.visible_node_render_order.is_empty());
assert!(query.visible_edge_ids.is_empty());
assert!(query.visible_edge_render_order.is_empty());
let changes = xyflow::NodeGraphChanges::from_patch(&root_patch);
assert!(changes.is_empty());
let _ = std::mem::size_of::<xyflow::XyFlowNodeElement>();
let _ = std::mem::size_of::<xyflow::XyFlowEdgeElement>();
let _ = std::mem::size_of::<xyflow::XyFlowNodeChange>();
let _ = std::mem::size_of::<xyflow::XyFlowEdgeChange>();
let _ = std::mem::size_of::<xyflow::XyFlowDimensionAttribute>();
let _ = std::mem::size_of::<xyflow::XyFlowDimensionsSetAttributes>();
let _: fn(
&[xyflow::XyFlowNodeChange],
&[xyflow::XyFlowNodeElement],
) -> Vec<xyflow::XyFlowNodeElement> = xyflow::apply_xyflow_node_changes;
let _: fn(
&[xyflow::XyFlowEdgeChange],
&[xyflow::XyFlowEdgeElement],
) -> Vec<xyflow::XyFlowEdgeElement> = xyflow::apply_xyflow_edge_changes;
let _ = std::mem::size_of::<xyflow::NodeDragUpdate>();
let _ = std::mem::size_of::<xyflow::NodeResizeStart>();
let _ = std::mem::size_of::<xyflow::NodeResizeUpdate>();
let _ = std::mem::size_of::<xyflow::NodeResizeEnd>();
let _ = std::mem::size_of::<xyflow::NodeResizeEndOutcome>();
}
#[test]
fn conformance_module_exposes_serde_friendly_headless_fixture_vocabulary() {
let graph = Graph::new(GraphId::new());
let node_id = NodeId::new();
let target = CanvasPoint { x: 32.0, y: 16.0 };
let drag_start = events::NodeDragStart {
primary: node_id,
nodes: vec![node_id],
pointer: CanvasPoint { x: 1.0, y: 2.0 },
};
let resize_start = events::NodeResizeStart {
node: node_id,
direction: resize::NodeResizeDirection::BottomRight,
pointer: CanvasPoint { x: 1.0, y: 1.0 },
};
let resize_update = events::NodeResizeUpdate {
node: node_id,
direction: resize::NodeResizeDirection::BottomRight,
pointer: CanvasPoint { x: 2.0, y: 3.0 },
position: CanvasPoint::default(),
size: CanvasSize {
width: 2.0,
height: 3.0,
},
};
let resize_end = events::NodeResizeEnd {
node: node_id,
direction: resize::NodeResizeDirection::BottomRight,
pointer: resize_update.pointer,
outcome: events::NodeResizeEndOutcome::Committed,
};
let resize_start_event = events::NodeGraphGestureEvent::NodeResizeStart(resize_start.clone());
let resize_update_event =
events::NodeGraphGestureEvent::NodeResizeUpdate(resize_update.clone());
let resize_end_event = events::NodeGraphGestureEvent::NodeResizeEnd(resize_end.clone());
let viewport_scroll_action = conformance::ConformanceAction::apply_viewport_scroll_gesture(
viewport::ViewportGestureContext::idle(),
viewport::ViewportScrollInput::new(
CanvasPoint { x: 1.0, y: 2.0 },
CanvasPoint { x: 4.0, y: 5.0 },
false,
1.25,
0.5,
4.0,
),
);
let viewport_reject_action =
conformance::ConformanceAction::expect_viewport_drag_pan_gesture_rejected(
viewport::ViewportGestureContext {
connection_in_progress: true,
..viewport::ViewportGestureContext::idle()
},
viewport::ViewportDragPanInput::new(
viewport::ViewportPointerButton::Right,
CanvasPoint { x: 4.0, y: 5.0 },
),
viewport::ViewportGestureRejection::ConnectionInProgress,
);
let auto_pan_action =
conformance::ConformanceAction::apply_auto_pan(auto_pan::AutoPanRequest::new(
auto_pan::AutoPanActivation::Always,
CanvasPoint { x: 99.0, y: 40.0 },
CanvasSize {
width: 100.0,
height: 80.0,
},
0.016,
));
let selection_auto_pan_action = conformance::ConformanceAction::apply_selection_auto_pan(
auto_pan::SelectionAutoPanRequest::new(
CanvasPoint { x: 99.0, y: 40.0 },
CanvasSize {
width: 100.0,
height: 80.0,
},
0.016,
),
);
let viewport_pan_constrained_action =
conformance::ConformanceAction::apply_viewport_pan_constrained(
viewport::ViewportPanRequest::new(CanvasPoint { x: 20.0, y: -10.0 }),
CanvasSize {
width: 100.0,
height: 80.0,
},
);
let viewport_zoom_constrained_action =
conformance::ConformanceAction::apply_viewport_zoom_constrained(
viewport::ViewportZoomRequest::new(CanvasPoint::default(), 1.5, 0.5, 4.0),
CanvasSize {
width: 100.0,
height: 80.0,
},
);
let viewport_animation_action = conformance::ConformanceAction::assert_viewport_animation_frame(
viewport::ViewportAnimationRequest::new(
viewport::ViewportTransform::new(CanvasPoint::default(), 1.0).expect("viewport"),
viewport::ViewportTransform::new(CanvasPoint { x: 10.0, y: 0.0 }, 2.0)
.expect("viewport"),
viewport::ViewportAnimationOptions::new(1.0),
),
0.5,
viewport::ViewportAnimationFrame {
elapsed_seconds: 0.5,
progress: 0.5,
eased_progress: 0.5,
transform: viewport::ViewportTransform::new(CanvasPoint { x: 5.0, y: 0.0 }, 1.5)
.expect("viewport"),
done: false,
},
);
let apply_viewport_animation_action =
conformance::ConformanceAction::apply_viewport_animation_frame(
viewport::ViewportAnimationRequest::new(
viewport::ViewportTransform::new(CanvasPoint::default(), 1.0).expect("viewport"),
viewport::ViewportTransform::new(CanvasPoint { x: 10.0, y: 0.0 }, 2.0)
.expect("viewport"),
viewport::ViewportAnimationOptions::new(1.0),
),
0.5,
);
let apply_viewport_animation_frames_action =
conformance::ConformanceAction::apply_viewport_animation_frames(
viewport::ViewportAnimationRequest::new(
viewport::ViewportTransform::new(CanvasPoint::default(), 1.0).expect("viewport"),
viewport::ViewportTransform::new(CanvasPoint { x: 10.0, y: 0.0 }, 2.0)
.expect("viewport"),
viewport::ViewportAnimationOptions::new(1.0),
),
[0.5, 1.0],
);
let rendering_query_action = conformance::ConformanceAction::assert_rendering_query(
CanvasSize {
width: 100.0,
height: 80.0,
},
rendering::RenderingQueryResult::default(),
);
let selection_box_input = selection::SelectionBoxInput::replace(CanvasRect {
origin: CanvasPoint::default(),
size: CanvasSize {
width: 10.0,
height: 10.0,
},
});
let selection_box_action =
conformance::ConformanceAction::apply_selection_box(selection_box_input);
let inertia_request = viewport::ViewportPanInertiaRequest::new(
viewport::ViewportTransform::new(CanvasPoint::default(), 1.0).expect("viewport"),
CanvasPoint { x: 500.0, y: 0.0 },
NodeGraphPanInertiaTuning {
enabled: true,
decay_per_s: 2.0,
min_speed: 100.0,
max_speed: 1000.0,
},
);
let inertia_frame = viewport::plan_viewport_pan_inertia(inertia_request.clone())
.expect("inertia plan")
.frame_at(0.25)
.expect("inertia frame");
let apply_viewport_pan_inertia_action =
conformance::ConformanceAction::apply_viewport_pan_inertia_frame(
inertia_request.clone(),
0.25,
);
let apply_viewport_pan_inertia_frames_action =
conformance::ConformanceAction::apply_viewport_pan_inertia_frames(
inertia_request.clone(),
[0.25, 0.5],
);
let assert_viewport_pan_inertia_action =
conformance::ConformanceAction::assert_viewport_pan_inertia_frame(
inertia_request,
0.25,
inertia_frame,
);
let expect_viewport_pan_inertia_rejected_action =
conformance::ConformanceAction::expect_viewport_pan_inertia_rejected(
viewport::ViewportPanInertiaRequest::new(
viewport::ViewportTransform::new(CanvasPoint::default(), 1.0).expect("viewport"),
CanvasPoint { x: 50.0, y: 0.0 },
NodeGraphPanInertiaTuning {
enabled: true,
decay_per_s: 2.0,
min_speed: 100.0,
max_speed: 1000.0,
},
),
);
let viewport_double_click_action =
conformance::ConformanceAction::assert_viewport_double_click_zoom(
viewport::ViewportDoubleClickZoomInput::new(
viewport::ViewportTransform::new(CanvasPoint::default(), 1.0).expect("viewport"),
CanvasPoint { x: 10.0, y: 10.0 },
2.0,
0.5,
4.0,
viewport::ViewportAnimationOptions::new(0.2),
),
viewport::ViewportAnimationPlan {
from: viewport::ViewportTransform::new(CanvasPoint::default(), 1.0)
.expect("viewport"),
to: viewport::ViewportTransform::new(CanvasPoint { x: -5.0, y: -5.0 }, 2.0)
.expect("viewport"),
duration_seconds: 0.2,
easing: viewport::ViewportAnimationEasing::CubicInOut,
},
);
let delete_key_action = conformance::ConformanceAction::apply_delete_selection_for_key(
keyboard_types::Code::Backspace,
);
let node_pointer_down_action =
conformance::ConformanceAction::apply_node_pointer_down_expect_claim(
node_id,
false,
CanvasPoint { x: 3.0, y: 4.0 },
drag::PointerGestureClaim::NodeDrag,
);
assert_eq!(node_pointer_down_action.kind(), "apply_node_pointer_down");
let assert_node_position_action = conformance::ConformanceAction::assert_node_position(
node_id,
CanvasPoint { x: 1.0, y: 2.0 },
);
let source_handle =
connection::ConnectionHandleRef::new(node_id, PortId::new(), PortDirection::Out);
let target_handle = connection::ConnectionTargetHandle::new(
connection::ConnectionHandleRef::new(NodeId::new(), PortId::new(), PortDirection::In),
true,
true,
);
let connection_target_input = connection::ConnectionTargetInput::new(
source_handle,
Some(target_handle),
NodeGraphConnectionMode::Strict,
true,
);
let connection_target_action = conformance::ConformanceAction::assert_connection_target(
connection_target_input,
connection::ResolvedConnectionTarget {
target: Some(target_handle),
connection: Some(connection::ConnectionHandleConnection {
source: source_handle,
target: target_handle.handle,
}),
is_handle_valid: true,
feedback: connection::ConnectionHandleValidity::Valid,
},
);
let connection_target_candidate = connection::ConnectionTargetCandidate::new(
target_handle,
CanvasRect {
origin: CanvasPoint::default(),
size: CanvasSize {
width: 100.0,
height: 80.0,
},
},
geometry::HandleBounds {
rect: CanvasRect {
origin: CanvasPoint::default(),
size: CanvasSize {
width: 10.0,
height: 10.0,
},
},
position: geometry::HandlePosition::Right,
},
);
let connection_target_candidates = [connection_target_candidate];
let connection_target_from_handles_action =
conformance::ConformanceAction::assert_connection_target_from_handles(
connection::ConnectionTargetFromHandlesInput::new(
CanvasPoint { x: 5.0, y: 5.0 },
10.0,
source_handle,
&connection_target_candidates,
NodeGraphConnectionMode::Strict,
),
connection::ResolvedConnectionTarget {
target: Some(target_handle),
connection: Some(connection::ConnectionHandleConnection {
source: source_handle,
target: target_handle.handle,
}),
is_handle_valid: true,
feedback: connection::ConnectionHandleValidity::Valid,
},
);
let connect_action = conformance::ConformanceAction::apply_connect_edge(
connection::ConnectEdgeRequest::new(
PortId::new(),
PortId::new(),
NodeGraphConnectionMode::Strict,
)
.with_edge_id(EdgeId::new()),
);
let reconnect_action = conformance::ConformanceAction::apply_reconnect_edge(
connection::ReconnectEdgeRequest::new(
EdgeId::new(),
EdgeEndpoint::To,
PortId::new(),
NodeGraphConnectionMode::Strict,
),
);
let dispatch_action = conformance::ConformanceAction::dispatch_transaction(
GraphTransaction::new().with_label("low-level graph fixture setup"),
);
let resize_action = conformance::ConformanceAction::apply_node_resize(
resize::NodeResizeRequest::new(
node_id,
CanvasSize {
width: 2.0,
height: 2.0,
},
)
.with_direction(resize::NodeResizeDirection::BottomRight),
);
let pointer_resize_action = conformance::ConformanceAction::apply_node_pointer_resize(
resize::NodePointerResizeRequest::new(
node_id,
CanvasPoint { x: 1.0, y: 1.0 },
CanvasPoint { x: 2.0, y: 3.0 },
resize::NodeResizeDirection::BottomRight,
),
);
let report_measurement_action = conformance::ConformanceAction::report_node_measurement(
measurement::NodeMeasurement::new(node_id).with_size(Some(CanvasSize {
width: 2.0,
height: 3.0,
})),
);
let assert_layout_facts_action = conformance::ConformanceAction::assert_layout_facts(
CanvasSize {
width: 100.0,
height: 100.0,
},
conformance::ConformanceLayoutFactsExpectation::new([node_id], Vec::<EdgeId>::new()),
);
let resize_start_gesture_action =
conformance::ConformanceAction::emit_gesture(resize_start_event.clone());
let resize_update_gesture_action =
conformance::ConformanceAction::emit_gesture(resize_update_event.clone());
let resize_end_gesture_action =
conformance::ConformanceAction::emit_gesture(resize_end_event.clone());
let encoded_fixture_actions = serde_json::to_value([
viewport_scroll_action,
viewport_reject_action,
auto_pan_action,
selection_auto_pan_action,
viewport_pan_constrained_action,
viewport_zoom_constrained_action,
viewport_animation_action,
apply_viewport_animation_action,
apply_viewport_animation_frames_action,
rendering_query_action,
selection_box_action,
apply_viewport_pan_inertia_action,
apply_viewport_pan_inertia_frames_action,
assert_viewport_pan_inertia_action,
expect_viewport_pan_inertia_rejected_action,
viewport_double_click_action,
delete_key_action,
assert_node_position_action,
connection_target_action,
connection_target_from_handles_action,
connect_action,
reconnect_action,
dispatch_action,
resize_action,
pointer_resize_action,
report_measurement_action,
assert_layout_facts_action,
resize_start_gesture_action,
resize_update_gesture_action,
resize_end_gesture_action,
])
.expect("serialize fixture actions");
assert!(encoded_fixture_actions.is_array());
let encoded_resize_trace = serde_json::to_value([
conformance::ConformanceTraceEvent::gesture(resize_start_event.clone()),
conformance::ConformanceTraceEvent::callback(
conformance::ConformanceCallbackEvent::NodeResizeStart(resize_start.clone()),
),
conformance::ConformanceTraceEvent::gesture(resize_update_event.clone()),
conformance::ConformanceTraceEvent::callback(
conformance::ConformanceCallbackEvent::NodeResize(resize_update.clone()),
),
conformance::ConformanceTraceEvent::gesture(resize_end_event.clone()),
conformance::ConformanceTraceEvent::callback(
conformance::ConformanceCallbackEvent::NodeResizeEnd(resize_end),
),
])
.expect("serialize resize trace events");
assert!(encoded_resize_trace.is_array());
let scenario = conformance::ConformanceScenario::new("public node drag fixture", graph)
.with_view_state(NodeGraphViewState::default())
.with_editor_config(NodeGraphEditorConfig::default())
.with_xyflow_callbacks()
.with_node_drag_session_contract(conformance::ConformanceNodeDragSessionContract::new(
node_id,
drag_start.pointer,
target,
));
assert_eq!(
scenario.schema_version,
conformance::CONFORMANCE_FIXTURE_SCHEMA_VERSION,
);
let scenario_json = serde_json::to_value(&scenario).expect("serialize public scenario");
assert_eq!(
scenario_json["setup"]["trace"]["record_xyflow_callbacks"],
true
);
assert!(scenario.actions.is_empty());
assert_eq!(scenario.behaviors.len(), 1);
assert_eq!(scenario.expanded_actions().len(), 1);
assert!(!scenario.expanded_expected_trace().is_empty());
let selection_box_contract = conformance::ConformanceSelectionBoxContract::new(
selection_box_input,
[node_id],
Vec::<EdgeId>::new(),
);
let selection_scenario = conformance::ConformanceScenario::new(
"public selection box fixture",
Graph::new(GraphId::new()),
)
.with_selection_box_contract(selection_box_contract);
assert_eq!(selection_scenario.behaviors.len(), 1);
assert_eq!(selection_scenario.expanded_actions().len(), 1);
assert!(!selection_scenario.expanded_expected_trace().is_empty());
let selection_fixture =
serde_json::to_value(&selection_scenario).expect("serialize selection fixture");
let selection_decoded: conformance::ConformanceScenario =
serde_json::from_value(selection_fixture).expect("deserialize selection fixture");
assert_eq!(selection_decoded.behaviors.len(), 1);
let delete_selection_contract = conformance::ConformanceDeleteSelectionContract::new(1, 0)
.for_key(keyboard_types::Code::Delete)
.with_commit_op_kinds(["remove_node"]);
let node_pointer_down_contract = conformance::ConformanceNodePointerDownSelectionContract::new(
selection::NodePointerDownInput::new(node_id, false, CanvasPoint { x: 3.0, y: 4.0 }),
drag::PointerGestureClaim::NodeDrag,
[node_id],
std::iter::empty::<EdgeId>(),
);
let node_pointer_down_scenario = conformance::ConformanceScenario::new(
"public node pointer down selection fixture",
Graph::new(GraphId::new()),
)
.with_node_pointer_down_selection_contract(node_pointer_down_contract);
assert_eq!(node_pointer_down_scenario.behaviors.len(), 1);
assert_eq!(node_pointer_down_scenario.expanded_actions().len(), 1);
assert!(
!node_pointer_down_scenario
.expanded_expected_trace()
.is_empty()
);
let node_pointer_down_fixture = serde_json::to_value(&node_pointer_down_scenario)
.expect("serialize node pointer down fixture");
let node_pointer_down_decoded: conformance::ConformanceScenario =
serde_json::from_value(node_pointer_down_fixture)
.expect("deserialize node pointer down fixture");
assert_eq!(node_pointer_down_decoded.behaviors.len(), 1);
let delete_during_drag_contract =
conformance::ConformanceDeleteSelectionDuringNodeDragContract::new(
events::NodeDragStart {
primary: node_id,
nodes: vec![node_id],
pointer: CanvasPoint { x: 1.0, y: 2.0 },
},
events::NodeDragEnd {
primary: node_id,
nodes: vec![node_id],
pointer: CanvasPoint { x: 1.0, y: 2.0 },
outcome: events::NodeDragEndOutcome::Canceled,
},
conformance::ConformanceDeleteSelectionContract::new(1, 1)
.for_key(keyboard_types::Code::Backspace)
.with_disconnected([xyflow::EdgeConnection::new(
EdgeId::new(),
PortId::new(),
PortId::new(),
EdgeKind::Data,
)]),
);
let delete_during_drag_scenario = conformance::ConformanceScenario::new(
"public delete during drag fixture",
Graph::new(GraphId::new()),
)
.with_delete_selection_during_node_drag_contract(delete_during_drag_contract);
assert_eq!(delete_during_drag_scenario.behaviors.len(), 1);
assert_eq!(delete_during_drag_scenario.expanded_actions().len(), 3);
assert!(
!delete_during_drag_scenario
.expanded_expected_trace()
.is_empty()
);
let delete_during_drag_fixture = serde_json::to_value(&delete_during_drag_scenario)
.expect("serialize delete during drag fixture");
let delete_during_drag_decoded: conformance::ConformanceScenario =
serde_json::from_value(delete_during_drag_fixture)
.expect("deserialize delete during drag fixture");
assert_eq!(delete_during_drag_decoded.behaviors.len(), 1);
let delete_scenario = conformance::ConformanceScenario::new(
"public delete selection fixture",
Graph::new(GraphId::new()),
)
.with_delete_selection_contract(delete_selection_contract);
assert_eq!(delete_scenario.behaviors.len(), 1);
assert_eq!(delete_scenario.expanded_actions().len(), 1);
assert!(!delete_scenario.expanded_expected_trace().is_empty());
let delete_fixture = serde_json::to_value(&delete_scenario).expect("serialize delete fixture");
let delete_decoded: conformance::ConformanceScenario =
serde_json::from_value(delete_fixture).expect("deserialize delete fixture");
assert_eq!(delete_decoded.behaviors.len(), 1);
let _ = std::mem::size_of::<conformance::ConformanceBehavior>();
let _ = std::mem::size_of::<conformance::ConformanceNodeResizeSessionContract>();
let _ = std::mem::size_of::<conformance::ConformanceRenderingQueryContract>();
let _ = std::mem::size_of::<conformance::ConformanceSelectionBoxContract>();
let _ = std::mem::size_of::<conformance::ConformanceDeleteSelectionContract>();
let _ = std::mem::size_of::<conformance::ConformanceDeleteSelectionDuringNodeDragContract>();
let _ = std::mem::size_of::<conformance::ConformanceNodePointerDownSelectionContract>();
let _ = std::mem::size_of::<selection::NodePointerDownInput>();
let _ = std::mem::size_of::<resize::NodeResizeRequest>();
let _ = std::mem::size_of::<resize::NodePointerResizeRequest>();
let _ = std::mem::size_of::<drag::NodeNudgeRequest>();
let _ = std::mem::size_of::<conformance::ConformanceLayoutFactsContract>();
let _ = std::mem::size_of::<conformance::ConformanceLayoutFactsExpectation>();
let _ = std::mem::size_of::<conformance::ConformanceLayoutEdgePosition>();
let _ = std::mem::size_of::<conformance::ConformanceEdgeEndpointPosition>();
let _ = std::mem::size_of::<conformance::ConformanceLayoutFactsConnectionTargetExpectation>();
let encoded = serde_json::to_value(&scenario).expect("serialize fixture");
let decoded: conformance::ConformanceScenario =
serde_json::from_value(encoded.clone()).expect("deserialize fixture");
assert_eq!(
serde_json::to_value(decoded).expect("reserialize fixture"),
encoded,
);
let empty_scenario =
conformance::ConformanceScenario::new("public empty fixture", Graph::new(GraphId::new()));
let capability_matrix = conformance::ConformanceCapabilityMatrix::for_adapter("public-adapter")
.with_claim(conformance::ConformanceCapabilityClaim::full(
conformance::ConformanceCapabilityKind::MeasuredAnchors,
))
.with_claim(conformance::ConformanceCapabilityClaim::projection(
conformance::ConformanceCapabilityKind::LayoutPassMeasurement,
));
assert!(capability_matrix.satisfies(
conformance::ConformanceCapabilityKind::MeasuredAnchors,
conformance::ConformanceSupportLevel::Full,
));
assert!(!capability_matrix.satisfies(
conformance::ConformanceCapabilityKind::LayoutPassMeasurement,
conformance::ConformanceSupportLevel::Full,
));
let capability_fixture =
serde_json::to_value(&capability_matrix).expect("serialize capability matrix");
let capability_decoded: conformance::ConformanceCapabilityMatrix =
serde_json::from_value(capability_fixture).expect("deserialize capability matrix");
assert_eq!(
capability_decoded.level(conformance::ConformanceCapabilityKind::LayoutPassMeasurement),
conformance::ConformanceSupportLevel::Projection
);
let mut suite = conformance::ConformanceSuite::new("public adapter suite")
.with_capabilities(capability_decoded);
suite.push_scenario(empty_scenario.clone());
let suite = suite.with_scenarios([empty_scenario.clone()]);
let suite_report = conformance::run_conformance_suite(&suite);
assert!(suite_report.is_match(), "{suite_report}");
assert_eq!(suite_report.scenario_count(), 1);
assert_eq!(suite_report.failed_scenarios(), 0);
assert_eq!(
suite_report
.capabilities
.level(conformance::ConformanceCapabilityKind::MeasuredAnchors),
conformance::ConformanceSupportLevel::Full
);
let overclaim_suite = conformance::ConformanceSuite::new("overclaim suite")
.with_capabilities(conformance::ConformanceCapabilityMatrix::for_adapter(
"overclaim-adapter",
))
.with_capability_requirement(conformance::ConformanceCapabilityRequirement::new(
conformance::ConformanceCapabilityKind::MeasuredAnchors,
conformance::ConformanceSupportLevel::Full,
))
.with_scenarios([empty_scenario.clone()]);
let overclaim_report = conformance::run_conformance_suite(&overclaim_suite);
assert!(!overclaim_report.is_match());
assert_eq!(overclaim_report.capability_gaps.len(), 1);
assert_eq!(
overclaim_report.capability_gaps[0].capability,
conformance::ConformanceCapabilityKind::MeasuredAnchors
);
let suite_encoded = serde_json::to_value(&suite).expect("serialize suite");
let suite_decoded: conformance::ConformanceSuite =
serde_json::from_value(suite_encoded.clone()).expect("deserialize suite");
assert_eq!(
serde_json::to_value(suite_decoded).expect("reserialize suite"),
suite_encoded,
);
let suite_path = std::env::temp_dir().join(format!(
"jellyflow-public-suite-{}.json",
uuid::Uuid::new_v4()
));
suite.save_json(&suite_path).expect("save suite json");
let loaded_suite =
conformance::ConformanceSuite::load_json(&suite_path).expect("load suite json");
assert!(loaded_suite.run().is_match());
assert!(
conformance::ConformanceSuite::load_json_if_exists(&suite_path)
.expect("optional suite json")
.is_some()
);
let _ = std::fs::remove_file(&suite_path);
assert!(
conformance::ConformanceSuite::load_json_if_exists(&suite_path)
.expect("missing optional suite json")
.is_none()
);
let _ = std::mem::size_of::<conformance::ConformanceFixtureFileError>();
let suite_approval = suite.approve_actual_traces();
assert!(suite_approval.is_approvable());
let suite_approval_encoded =
serde_json::to_value(&suite_approval.report).expect("serialize suite approval report");
let suite_approval_decoded: conformance::ConformanceSuiteApprovalReport =
serde_json::from_value(suite_approval_encoded).expect("deserialize suite approval report");
assert!(suite_approval_decoded.is_approvable());
let fixture_root = std::env::temp_dir().join(format!(
"jellyflow-public-fixtures-{}",
uuid::Uuid::new_v4()
));
std::fs::create_dir_all(&fixture_root).expect("create fixture root");
suite
.save_json(fixture_root.join("suite.json"))
.expect("save directory suite json");
let fixture_directory = conformance::ConformanceFixtureDirectory::load_json(&fixture_root)
.expect("load fixture directory");
assert_eq!(fixture_directory.file_count(), 1);
let file_approval =
conformance::ConformanceSuiteFile::load_json(fixture_root.join("suite.json"))
.expect("load suite file")
.approve_actual_traces_to_json()
.expect("approve suite file");
assert!(file_approval.is_approvable());
let directory_approval = fixture_directory
.approve_actual_traces_to_json()
.expect("approve fixture directory");
assert!(directory_approval.is_approvable());
let fixture_report = fixture_directory.run();
assert!(fixture_report.is_match());
assert_eq!(fixture_report.failed_files(), 0);
let fixture_report_encoded =
serde_json::to_value(&fixture_report).expect("serialize fixture directory report");
let fixture_report_decoded: conformance::ConformanceFixtureDirectoryReport =
serde_json::from_value(fixture_report_encoded)
.expect("deserialize fixture directory report");
assert!(fixture_report_decoded.is_match());
assert!(
conformance::ConformanceFixtureDirectory::load_json_if_exists(&fixture_root)
.expect("optional fixture directory")
.is_some()
);
let _ = std::fs::remove_dir_all(&fixture_root);
assert!(
conformance::ConformanceFixtureDirectory::load_json_if_exists(&fixture_root)
.expect("missing optional fixture directory")
.is_none()
);
let _ = std::mem::size_of::<conformance::ConformanceSuiteFile>();
let _ = std::mem::size_of::<conformance::ConformanceSuiteFileReport>();
let _ = std::mem::size_of::<conformance::ConformanceFixtureDirectoryReport>();
let _ = std::mem::size_of::<conformance::ConformanceSuiteApproval>();
let _ = std::mem::size_of::<conformance::ConformanceSuiteApprovalReport>();
let _ = std::mem::size_of::<conformance::ConformanceScenarioApprovalReport>();
let _ = std::mem::size_of::<conformance::ConformanceSuiteFileApprovalReport>();
let _ = std::mem::size_of::<conformance::ConformanceFixtureDirectoryApprovalReport>();
let _ = std::mem::size_of::<conformance::ConformanceApprovalError>();
}