jellyflow_runtime/runtime/conformance/scenario/
suite.rs1use serde::{Deserialize, Serialize};
2
3use jellyflow_core::core::Graph;
4
5use super::ConformanceScenarioCompiled;
6use super::action::ConformanceAction;
7use super::behavior::ConformanceBehavior;
8use super::constants::default_schema_version;
9use super::setup::{ConformanceSetup, ConformanceTraceConfig};
10use super::trace::ConformanceTraceEvent;
11use crate::runtime::conformance::capability::{
12 ConformanceCapabilityMatrix, ConformanceCapabilityRequirement,
13};
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ConformanceScenario {
17 #[serde(default = "default_schema_version")]
18 pub schema_version: u32,
19 pub name: String,
20 #[serde(default)]
21 setup: ConformanceSetup,
22 #[serde(default, skip_serializing_if = "Vec::is_empty")]
23 pub actions: Vec<ConformanceAction>,
24 #[serde(default, skip_serializing_if = "Vec::is_empty")]
25 pub behaviors: Vec<ConformanceBehavior>,
26 #[serde(default, skip_serializing_if = "Vec::is_empty")]
27 pub expected_trace: Vec<ConformanceTraceEvent>,
28}
29
30impl ConformanceScenario {
31 pub fn new(name: impl Into<String>, graph: Graph) -> Self {
32 Self {
33 schema_version: default_schema_version(),
34 name: name.into(),
35 setup: ConformanceSetup::from_graph(graph),
36 actions: Vec::new(),
37 behaviors: Vec::new(),
38 expected_trace: Vec::new(),
39 }
40 }
41
42 pub fn with_view_state(mut self, view_state: crate::io::NodeGraphViewState) -> Self {
43 self.setup.view_state = view_state;
44 self
45 }
46
47 pub fn with_editor_config(mut self, editor_config: crate::io::NodeGraphEditorConfig) -> Self {
48 self.setup.editor_config = editor_config;
49 self
50 }
51
52 pub fn with_xyflow_callbacks(mut self) -> Self {
53 self.setup.trace = ConformanceTraceConfig::with_xyflow_callbacks();
54 self
55 }
56
57 pub fn with_actions(mut self, actions: impl IntoIterator<Item = ConformanceAction>) -> Self {
58 self.actions = actions.into_iter().collect();
59 self
60 }
61
62 pub fn with_behaviors(
63 mut self,
64 behaviors: impl IntoIterator<Item = ConformanceBehavior>,
65 ) -> Self {
66 self.behaviors = behaviors.into_iter().collect();
67 self
68 }
69
70 pub fn with_behavior(mut self, behavior: ConformanceBehavior) -> Self {
71 self.behaviors.push(behavior);
72 self
73 }
74
75 pub fn with_expected_trace(
76 mut self,
77 expected_trace: impl IntoIterator<Item = ConformanceTraceEvent>,
78 ) -> Self {
79 self.expected_trace = expected_trace.into_iter().collect();
80 self
81 }
82
83 pub(crate) fn compiled(&self) -> ConformanceScenarioCompiled {
84 ConformanceScenarioCompiled::from_scenario(self)
85 }
86
87 pub(crate) fn setup(&self) -> &ConformanceSetup {
88 &self.setup
89 }
90
91 pub fn expanded_actions(&self) -> Vec<ConformanceAction> {
92 self.compiled().actions().to_vec()
93 }
94
95 pub fn expanded_expected_trace(&self) -> Vec<ConformanceTraceEvent> {
96 self.compiled().expected_trace().to_vec()
97 }
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct ConformanceSuite {
102 #[serde(default = "default_schema_version")]
103 pub schema_version: u32,
104 pub name: String,
105 #[serde(default, skip_serializing_if = "ConformanceCapabilityMatrix::is_empty")]
106 pub capabilities: ConformanceCapabilityMatrix,
107 #[serde(default, skip_serializing_if = "Vec::is_empty")]
108 pub capability_requirements: Vec<ConformanceCapabilityRequirement>,
109 #[serde(default, skip_serializing_if = "Vec::is_empty")]
110 pub scenarios: Vec<ConformanceScenario>,
111}
112
113impl ConformanceSuite {
114 pub fn new(name: impl Into<String>) -> Self {
115 Self {
116 schema_version: default_schema_version(),
117 name: name.into(),
118 capabilities: ConformanceCapabilityMatrix::default(),
119 capability_requirements: Vec::new(),
120 scenarios: Vec::new(),
121 }
122 }
123
124 pub fn with_capabilities(mut self, capabilities: ConformanceCapabilityMatrix) -> Self {
125 self.capabilities = capabilities;
126 self
127 }
128
129 pub fn with_capability_requirement(
130 mut self,
131 requirement: ConformanceCapabilityRequirement,
132 ) -> Self {
133 self.capability_requirements.push(requirement);
134 self
135 }
136
137 pub fn with_capability_requirements(
138 mut self,
139 requirements: impl IntoIterator<Item = ConformanceCapabilityRequirement>,
140 ) -> Self {
141 self.capability_requirements.extend(requirements);
142 self
143 }
144
145 pub fn with_scenarios(
146 mut self,
147 scenarios: impl IntoIterator<Item = ConformanceScenario>,
148 ) -> Self {
149 self.scenarios = scenarios.into_iter().collect();
150 self
151 }
152
153 pub fn push_scenario(&mut self, scenario: ConformanceScenario) {
154 self.scenarios.push(scenario);
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::runtime::conformance::ConformanceNodeDragSessionContract;
162 use jellyflow_core::core::{CanvasPoint, Graph, GraphId, NodeId};
163
164 #[test]
165 fn compiled_scenario_strips_behavior_trace_suffix_from_approved_trace() {
166 let node_id = NodeId::from_u128(1);
167 let scenario = ConformanceScenario::new("compiled approval", Graph::new(GraphId::new()))
168 .with_behavior(ConformanceBehavior::node_drag_session(
169 ConformanceNodeDragSessionContract::new(
170 node_id,
171 CanvasPoint { x: 1.0, y: 2.0 },
172 CanvasPoint { x: 3.0, y: 4.0 },
173 ),
174 ));
175 let compiled = scenario.compiled();
176 let approved =
177 compiled.approval_expected_trace(&scenario.expected_trace, compiled.expected_trace());
178
179 assert!(scenario.expected_trace.is_empty());
180 assert!(approved.is_empty());
181 assert_eq!(
182 compiled.expected_event_count(),
183 compiled.expected_trace().len()
184 );
185 assert_eq!(compiled.actions().len(), 1);
186 }
187}