1use super::event::PipelinePhase;
8use serde::{Deserialize, Serialize};
9
10#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
15pub enum XmlOutputType {
16 DevelopmentResult,
18 DevelopmentPlan,
20 ReviewIssues,
22 FixResult,
24 CommitMessage,
26}
27
28#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
33pub struct XmlOutputContext {
34 pub iteration: Option<u32>,
36 pub pass: Option<u32>,
38 #[serde(default)]
43 pub snippets: Vec<XmlCodeSnippet>,
44}
45
46#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
48pub struct XmlCodeSnippet {
49 pub file: String,
51 pub line_start: u32,
53 pub line_end: u32,
55 pub content: String,
57}
58
59#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
64pub enum UIEvent {
65 PhaseTransition {
67 from: Option<PipelinePhase>,
68 to: PipelinePhase,
69 },
70
71 IterationProgress { current: u32, total: u32 },
73
74 ReviewProgress { pass: u32, total: u32 },
76
77 AgentActivity { agent: String, message: String },
79
80 XmlOutput {
85 xml_type: XmlOutputType,
87 content: String,
89 context: Option<XmlOutputContext>,
91 },
92}
93
94impl UIEvent {
95 pub fn phase_emoji(phase: &PipelinePhase) -> &'static str {
97 match phase {
98 PipelinePhase::Planning => "π",
99 PipelinePhase::Development => "π¨",
100 PipelinePhase::Review => "π",
101 PipelinePhase::CommitMessage => "π",
102 PipelinePhase::FinalValidation => "β
",
103 PipelinePhase::Finalizing => "π",
104 PipelinePhase::Complete => "π",
105 PipelinePhase::AwaitingDevFix => "π§",
106 PipelinePhase::Interrupted => "βΈοΈ",
107 }
108 }
109
110 pub fn format_for_display(&self) -> String {
115 crate::rendering::render_ui_event(self)
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn test_phase_transition_display() {
125 let event = UIEvent::PhaseTransition {
126 from: Some(PipelinePhase::Planning),
127 to: PipelinePhase::Development,
128 };
129 let display = event.format_for_display();
130 assert!(display.contains("π¨"));
131 assert!(display.contains("Development"));
132 }
133
134 #[test]
135 fn test_iteration_progress_display() {
136 let event = UIEvent::IterationProgress {
137 current: 2,
138 total: 5,
139 };
140 let display = event.format_for_display();
141 assert!(display.contains("2/5"));
142 }
143
144 #[test]
145 fn test_review_progress_display() {
146 let event = UIEvent::ReviewProgress { pass: 1, total: 3 };
147 let display = event.format_for_display();
148 assert!(display.contains("1/3"));
149 assert!(display.contains("Review pass"));
150 }
151
152 #[test]
153 fn test_agent_activity_display() {
154 let event = UIEvent::AgentActivity {
155 agent: "claude".to_string(),
156 message: "Processing request".to_string(),
157 };
158 let display = event.format_for_display();
159 assert!(display.contains("[claude]"));
160 assert!(display.contains("Processing request"));
161 }
162
163 #[test]
164 fn test_ui_event_serialization() {
165 let event = UIEvent::PhaseTransition {
166 from: None,
167 to: PipelinePhase::Planning,
168 };
169 let json = serde_json::to_string(&event).unwrap();
170 let deserialized: UIEvent = serde_json::from_str(&json).unwrap();
171 assert_eq!(event, deserialized);
172 }
173
174 #[test]
175 fn test_phase_emoji_all_phases() {
176 assert_eq!(UIEvent::phase_emoji(&PipelinePhase::Planning), "π");
178 assert_eq!(UIEvent::phase_emoji(&PipelinePhase::Development), "π¨");
179 assert_eq!(UIEvent::phase_emoji(&PipelinePhase::Review), "π");
180 assert_eq!(UIEvent::phase_emoji(&PipelinePhase::CommitMessage), "π");
181 assert_eq!(UIEvent::phase_emoji(&PipelinePhase::FinalValidation), "β
");
182 assert_eq!(UIEvent::phase_emoji(&PipelinePhase::Finalizing), "π");
183 assert_eq!(UIEvent::phase_emoji(&PipelinePhase::Complete), "π");
184 assert_eq!(UIEvent::phase_emoji(&PipelinePhase::AwaitingDevFix), "π§");
185 assert_eq!(UIEvent::phase_emoji(&PipelinePhase::Interrupted), "βΈοΈ");
186 }
187
188 #[test]
189 fn test_phase_transition_from_none() {
190 let event = UIEvent::PhaseTransition {
192 from: None,
193 to: PipelinePhase::Planning,
194 };
195 let display = event.format_for_display();
196 assert!(display.contains("π"));
197 assert!(display.contains("Planning"));
198 }
199
200 #[test]
205 fn test_xml_output_type_serialization() {
206 let xml_type = XmlOutputType::DevelopmentResult;
207 let json = serde_json::to_string(&xml_type).unwrap();
208 let deserialized: XmlOutputType = serde_json::from_str(&json).unwrap();
209 assert_eq!(xml_type, deserialized);
210 }
211
212 #[test]
213 fn test_xml_output_context_default() {
214 let context = XmlOutputContext::default();
215 assert!(context.iteration.is_none());
216 assert!(context.pass.is_none());
217 assert!(context.snippets.is_empty());
218 }
219
220 #[test]
221 fn test_xml_output_context_with_values() {
222 let context = XmlOutputContext {
223 iteration: Some(2),
224 pass: Some(1),
225 snippets: Vec::new(),
226 };
227 assert_eq!(context.iteration, Some(2));
228 assert_eq!(context.pass, Some(1));
229 }
230
231 #[test]
232 fn test_xml_output_event_serialization() {
233 let event = UIEvent::XmlOutput {
234 xml_type: XmlOutputType::ReviewIssues,
235 content: "<ralph-issues><ralph-issue>Test</ralph-issue></ralph-issues>".to_string(),
236 context: Some(XmlOutputContext {
237 iteration: None,
238 pass: Some(1),
239 snippets: Vec::new(),
240 }),
241 };
242 let json = serde_json::to_string(&event).unwrap();
243 let deserialized: UIEvent = serde_json::from_str(&json).unwrap();
244 assert_eq!(event, deserialized);
245 }
246
247 #[test]
248 fn test_xml_output_types_all_variants() {
249 let variants = [
251 XmlOutputType::DevelopmentResult,
252 XmlOutputType::DevelopmentPlan,
253 XmlOutputType::ReviewIssues,
254 XmlOutputType::FixResult,
255 XmlOutputType::CommitMessage,
256 ];
257 for (i, v1) in variants.iter().enumerate() {
258 for (j, v2) in variants.iter().enumerate() {
259 if i == j {
260 assert_eq!(v1, v2);
261 } else {
262 assert_ne!(v1, v2);
263 }
264 }
265 }
266 }
267}