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::Interrupted => "βΈοΈ",
106 }
107 }
108
109 pub fn format_for_display(&self) -> String {
111 match self {
112 UIEvent::PhaseTransition { to, .. } => {
113 format!("{} {}", Self::phase_emoji(to), to)
114 }
115 UIEvent::IterationProgress { current, total } => {
116 format!("π Development iteration {}/{}", current, total)
117 }
118 UIEvent::ReviewProgress { pass, total } => {
119 format!("π Review pass {}/{}", pass, total)
120 }
121 UIEvent::AgentActivity { agent, message } => {
122 format!("π€ [{}] {}", agent, message)
123 }
124 UIEvent::XmlOutput {
125 xml_type,
126 content,
127 context,
128 } => {
129 crate::reducer::xml_renderer::render_xml(xml_type, content, context)
131 }
132 }
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn test_phase_transition_display() {
142 let event = UIEvent::PhaseTransition {
143 from: Some(PipelinePhase::Planning),
144 to: PipelinePhase::Development,
145 };
146 let display = event.format_for_display();
147 assert!(display.contains("π¨"));
148 assert!(display.contains("Development"));
149 }
150
151 #[test]
152 fn test_iteration_progress_display() {
153 let event = UIEvent::IterationProgress {
154 current: 2,
155 total: 5,
156 };
157 let display = event.format_for_display();
158 assert!(display.contains("2/5"));
159 }
160
161 #[test]
162 fn test_review_progress_display() {
163 let event = UIEvent::ReviewProgress { pass: 1, total: 3 };
164 let display = event.format_for_display();
165 assert!(display.contains("1/3"));
166 assert!(display.contains("Review pass"));
167 }
168
169 #[test]
170 fn test_agent_activity_display() {
171 let event = UIEvent::AgentActivity {
172 agent: "claude".to_string(),
173 message: "Processing request".to_string(),
174 };
175 let display = event.format_for_display();
176 assert!(display.contains("[claude]"));
177 assert!(display.contains("Processing request"));
178 }
179
180 #[test]
181 fn test_ui_event_serialization() {
182 let event = UIEvent::PhaseTransition {
183 from: None,
184 to: PipelinePhase::Planning,
185 };
186 let json = serde_json::to_string(&event).unwrap();
187 let deserialized: UIEvent = serde_json::from_str(&json).unwrap();
188 assert_eq!(event, deserialized);
189 }
190
191 #[test]
192 fn test_phase_emoji_all_phases() {
193 assert_eq!(UIEvent::phase_emoji(&PipelinePhase::Planning), "π");
195 assert_eq!(UIEvent::phase_emoji(&PipelinePhase::Development), "π¨");
196 assert_eq!(UIEvent::phase_emoji(&PipelinePhase::Review), "π");
197 assert_eq!(UIEvent::phase_emoji(&PipelinePhase::CommitMessage), "π");
198 assert_eq!(UIEvent::phase_emoji(&PipelinePhase::FinalValidation), "β
");
199 assert_eq!(UIEvent::phase_emoji(&PipelinePhase::Finalizing), "π");
200 assert_eq!(UIEvent::phase_emoji(&PipelinePhase::Complete), "π");
201 assert_eq!(UIEvent::phase_emoji(&PipelinePhase::Interrupted), "βΈοΈ");
202 }
203
204 #[test]
205 fn test_phase_transition_from_none() {
206 let event = UIEvent::PhaseTransition {
208 from: None,
209 to: PipelinePhase::Planning,
210 };
211 let display = event.format_for_display();
212 assert!(display.contains("π"));
213 assert!(display.contains("Planning"));
214 }
215
216 #[test]
221 fn test_xml_output_type_serialization() {
222 let xml_type = XmlOutputType::DevelopmentResult;
223 let json = serde_json::to_string(&xml_type).unwrap();
224 let deserialized: XmlOutputType = serde_json::from_str(&json).unwrap();
225 assert_eq!(xml_type, deserialized);
226 }
227
228 #[test]
229 fn test_xml_output_context_default() {
230 let context = XmlOutputContext::default();
231 assert!(context.iteration.is_none());
232 assert!(context.pass.is_none());
233 assert!(context.snippets.is_empty());
234 }
235
236 #[test]
237 fn test_xml_output_context_with_values() {
238 let context = XmlOutputContext {
239 iteration: Some(2),
240 pass: Some(1),
241 snippets: Vec::new(),
242 };
243 assert_eq!(context.iteration, Some(2));
244 assert_eq!(context.pass, Some(1));
245 }
246
247 #[test]
248 fn test_xml_output_event_serialization() {
249 let event = UIEvent::XmlOutput {
250 xml_type: XmlOutputType::ReviewIssues,
251 content: "<ralph-issues><ralph-issue>Test</ralph-issue></ralph-issues>".to_string(),
252 context: Some(XmlOutputContext {
253 iteration: None,
254 pass: Some(1),
255 snippets: Vec::new(),
256 }),
257 };
258 let json = serde_json::to_string(&event).unwrap();
259 let deserialized: UIEvent = serde_json::from_str(&json).unwrap();
260 assert_eq!(event, deserialized);
261 }
262
263 #[test]
264 fn test_xml_output_types_all_variants() {
265 let variants = [
267 XmlOutputType::DevelopmentResult,
268 XmlOutputType::DevelopmentPlan,
269 XmlOutputType::ReviewIssues,
270 XmlOutputType::FixResult,
271 XmlOutputType::CommitMessage,
272 ];
273 for (i, v1) in variants.iter().enumerate() {
274 for (j, v2) in variants.iter().enumerate() {
275 if i == j {
276 assert_eq!(v1, v2);
277 } else {
278 assert_ne!(v1, v2);
279 }
280 }
281 }
282 }
283}