ralph_workflow/rendering/
ui_event.rs1use crate::reducer::ui_event::UIEvent;
7
8#[must_use]
13pub fn render_ui_event(event: &UIEvent) -> String {
14 match event {
15 UIEvent::PhaseTransition { to, .. } => {
16 format!("{} {}", UIEvent::phase_emoji(to), to)
17 }
18 UIEvent::IterationProgress { current, total } => {
19 format!("🔄 Development iteration {current}/{total}")
20 }
21 UIEvent::ReviewProgress { pass, total } => {
22 format!("👁 Review pass {pass}/{total}")
23 }
24 UIEvent::AgentActivity { agent, message } => {
25 format!("🤖 [{agent}] {message}")
26 }
27 UIEvent::PushCompleted {
28 remote,
29 branch,
30 commit_sha,
31 } => {
32 let short = &commit_sha[..7.min(commit_sha.len())];
33 format!("⬆️ Pushed {short} to {remote}/{branch}")
34 }
35 UIEvent::PushFailed {
36 remote,
37 branch,
38 error,
39 } => {
40 format!("⚠️ Push failed for {remote}/{branch}: {error}")
41 }
42 UIEvent::PullRequestCreated { url, number } => {
43 if *number > 0 {
44 format!("🔀 PR created #{number}: {url}")
45 } else {
46 format!("🔀 PR created: {url}")
47 }
48 }
49 UIEvent::PullRequestFailed { error } => {
50 format!("⚠️ PR creation failed: {error}")
51 }
52 UIEvent::XmlOutput {
53 xml_type,
54 content,
55 context,
56 } => super::xml::render_xml(xml_type, content, context),
57 UIEvent::PromptReplayHit { key, was_replayed } => {
58 if *was_replayed {
59 format!("📋 [prompt-replay] Replayed stored prompt: {key}")
60 } else {
61 format!("📋 [prompt-replay] Generated fresh prompt: {key}")
62 }
63 }
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use crate::reducer::event::PipelinePhase;
71 use crate::reducer::ui_event::{XmlOutputContext, XmlOutputType};
72
73 #[test]
74 fn test_render_phase_transition() {
75 let event = UIEvent::PhaseTransition {
76 from: Some(PipelinePhase::Planning),
77 to: PipelinePhase::Development,
78 };
79 let output = render_ui_event(&event);
80 assert!(output.contains("🔨"));
81 assert!(output.contains("Development"));
82 }
83
84 #[test]
85 fn test_render_iteration_progress() {
86 let event = UIEvent::IterationProgress {
87 current: 2,
88 total: 5,
89 };
90 let output = render_ui_event(&event);
91 assert!(output.contains("2/5"));
92 assert!(output.contains("🔄"));
93 }
94
95 #[test]
96 fn test_render_review_progress() {
97 let event = UIEvent::ReviewProgress { pass: 1, total: 3 };
98 let output = render_ui_event(&event);
99 assert!(output.contains("1/3"));
100 assert!(output.contains("👁"));
101 }
102
103 #[test]
104 fn test_render_agent_activity() {
105 let event = UIEvent::AgentActivity {
106 agent: "claude".to_string(),
107 message: "Processing request".to_string(),
108 };
109 let output = render_ui_event(&event);
110 assert!(output.contains("[claude]"));
111 assert!(output.contains("Processing request"));
112 }
113
114 #[test]
115 fn test_render_xml_output_routes_to_xml_module() {
116 let event = UIEvent::XmlOutput {
117 xml_type: XmlOutputType::DevelopmentResult,
118 content: r"<ralph-development-result>
119<ralph-status>completed</ralph-status>
120<ralph-summary>Done</ralph-summary>
121</ralph-development-result>"
122 .to_string(),
123 context: Some(XmlOutputContext::default()),
124 };
125 let output = render_ui_event(&event);
126 assert!(output.contains("✅") || output.contains("Completed"));
128 assert!(output.contains("Done"));
129 }
130
131 #[test]
132 fn test_phase_emoji_via_ui_event() {
133 let phases = [
135 PipelinePhase::Planning,
136 PipelinePhase::Development,
137 PipelinePhase::Review,
138 PipelinePhase::CommitMessage,
139 PipelinePhase::FinalValidation,
140 PipelinePhase::Finalizing,
141 PipelinePhase::Complete,
142 PipelinePhase::Interrupted,
143 ];
144 phases.into_iter().for_each(|phase| {
145 let emoji = UIEvent::phase_emoji(&phase);
146 assert!(!emoji.is_empty(), "Phase {phase:?} should have an emoji");
147 });
148 }
149}