fluent_test/frontend/
console.rs

1use crate::backend::LogicalOp;
2use crate::backend::{Assertion, TestSessionResult};
3use crate::config::Config;
4use colored::*;
5
6/// Handles rendering of test results to the console
7pub struct ConsoleRenderer {
8    config: Config,
9}
10
11impl ConsoleRenderer {
12    /// Create a new renderer with the provided configuration
13    pub fn new(config: Config) -> Self {
14        Self { config }
15    }
16
17    /// Render a successful assertion result
18    pub fn render_success(&self, result: &Assertion<()>) -> String {
19        let message = self.build_assertion_message(result);
20
21        if self.config.show_success_details {
22            let prefix = if self.config.use_unicode_symbols { "✓ " } else { "+ " };
23            if self.config.use_colors {
24                return format!("{}{}", prefix.green(), message.green());
25            } else {
26                return format!("{}{}", prefix, message);
27            }
28        } else {
29            return String::new(); // Empty string when not showing success details
30        }
31    }
32
33    /// Render a failed assertion result
34    pub fn render_failure(&self, result: &Assertion<()>) -> (String, String) {
35        let message = self.build_assertion_message(result);
36        let details = self.build_failure_details(result);
37
38        let prefix = if self.config.use_unicode_symbols { "✗ " } else { "- " };
39        let header = if self.config.use_colors { format!("{}{}", prefix, message.red().bold()) } else { format!("{}{}", prefix, message) };
40
41        return (header, details);
42    }
43
44    /// Build a failure details string
45    fn build_failure_details(&self, result: &Assertion<()>) -> String {
46        let mut details = String::new();
47
48        // Add individual step results with proper formatting
49        for step in &result.steps {
50            let result_symbol = if step.passed { "✓" } else { "✗" };
51            // For individual steps, conjugate based on the subject name
52            let formatted_sentence = step.sentence.format_with_conjugation(result.expr_str);
53
54            // Always indent and add pass/fail prefix
55            details.push_str(&format!("  {} {}\n", result_symbol, formatted_sentence));
56        }
57
58        return details;
59    }
60
61    /// Build the main assertion message
62    fn build_assertion_message(&self, result: &Assertion<()>) -> String {
63        if result.steps.is_empty() {
64            return "No assertions made".to_string();
65        }
66
67        // Clean expression string (remove reference symbols)
68        let clean_expr = result.expr_str.trim_start_matches('&');
69
70        // For single assertions, conjugate based on the subject name
71        if result.steps.len() == 1 {
72            return format!("{} {}", clean_expr, result.steps[0].sentence.format_with_conjugation(result.expr_str));
73        }
74
75        // Start with the first step and conjugate based on the subject
76        let mut message = format!("{} {}", clean_expr, result.steps[0].sentence.format_with_conjugation(result.expr_str));
77
78        // Add remaining steps with logical operators
79        for i in 1..result.steps.len() {
80            let prev = &result.steps[i - 1];
81            let curr = &result.steps[i];
82
83            let op_str = match prev.logical_op {
84                Some(LogicalOp::And) => " AND ",
85                Some(LogicalOp::Or) => " OR ",
86                None => " [MISSING OP] ",
87            };
88
89            // For all subsequent parts in a chain, use conjugated verbs with grammatical format for consistency
90            // This makes phrases like "is greater than X AND is less than Y" instead of "is greater than X AND be less than Y"
91            message.push_str(&format!("{}{}", op_str, curr.sentence.format_with_conjugation(result.expr_str)));
92        }
93
94        return message;
95    }
96
97    /// Render a full test session result
98    pub fn render_session_summary(&self, result: &TestSessionResult) -> String {
99        let mut output = String::from("\nTest Results:\n");
100
101        let passed_msg = format!("{} passed", result.passed_count);
102        let failed_msg = format!("{} failed", result.failed_count);
103
104        if self.config.use_colors {
105            output.push_str(&format!(
106                "  {} / {}\n",
107                if result.passed_count > 0 { passed_msg.green() } else { passed_msg.normal() },
108                if result.failed_count > 0 { failed_msg.red().bold() } else { failed_msg.normal() }
109            ));
110        } else {
111            output.push_str(&format!("  {} / {}\n", passed_msg, failed_msg));
112        }
113
114        if result.failed_count > 0 {
115            output.push_str("\nFailure Details:\n");
116            for (i, failure) in result.failures.iter().enumerate() {
117                let (header, details) = self.render_failure(failure);
118                output.push_str(&format!("  {}. {}\n", i + 1, header));
119
120                // Process each line of the details with indentation
121                for line in details.lines() {
122                    output.push_str(&format!("     {}\n", line));
123                }
124            }
125        }
126
127        return output;
128    }
129
130    /// Format and print a successful test result to the console
131    pub fn print_success(&self, result: &Assertion<()>) {
132        let message = self.render_success(result);
133        if !message.is_empty() {
134            println!("{}", message);
135        }
136    }
137
138    /// Format and print a failed test result to the console
139    pub fn print_failure(&self, result: &Assertion<()>) {
140        let (header, details) = self.render_failure(result);
141
142        // Print the main error message
143        println!("{}", header);
144
145        // Print the details with appropriate colors
146        if self.config.use_colors {
147            for line in details.lines() {
148                if line.contains("✓") {
149                    println!("{}", line.green());
150                } else if line.contains("✗") {
151                    println!("{}", line.red());
152                } else {
153                    println!("{}", line);
154                }
155            }
156        } else {
157            // Print without colors
158            println!("{}", details);
159        }
160    }
161
162    /// Print the complete test session summary
163    pub fn print_session_summary(&self, result: &TestSessionResult) {
164        println!("{}", self.render_session_summary(result));
165    }
166}