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