Skip to main content

jellyflow_runtime/runtime/conformance/scenario/
suite.rs

1use 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}