Skip to main content

aprender_test_showcase/
driver.rs

1//! Unified Calculator Driver - The Probar Way
2//!
3//! This module implements the core unification principle:
4//! **Write the test logic once, run it everywhere.**
5//!
6//! Probar: Balanced testing - Balanced testing across platforms
7
8use crate::core::{CalcError, CalcResult};
9
10/// Abstract driver trait for calculator interactions
11///
12/// This trait defines the interface that both TUI and WASM drivers implement,
13/// enabling unified test specifications that work on any platform.
14///
15/// # Example
16///
17/// ```rust,ignore
18/// // The abstract test specification
19/// async fn verify_calculation<D: CalculatorDriver>(driver: &mut D) {
20///     driver.enter_expression("10 * (5 + 5)").await;
21///     assert_eq!(driver.get_result().await, "100");
22/// }
23///
24/// // TUI test
25/// #[test]
26/// fn test_tui() {
27///     let mut driver = TuiDriver::new();
28///     block_on(verify_calculation(&mut driver));
29/// }
30///
31/// // WASM test
32/// #[wasm_bindgen_test]
33/// async fn test_wasm() {
34///     let mut driver = WasmDriver::new().await;
35///     verify_calculation(&mut driver).await;
36/// }
37/// ```
38pub trait CalculatorDriver {
39    /// Enters an expression into the calculator
40    fn enter_expression(&mut self, expr: &str) -> CalcResult<()>;
41
42    /// Gets the current result display
43    fn get_result(&self) -> String;
44
45    /// Gets the current input expression
46    fn get_input(&self) -> String;
47
48    /// Clears the calculator state
49    fn clear(&mut self);
50
51    /// Gets history entries (newest first)
52    fn get_history(&self) -> Vec<HistoryItem>;
53
54    /// Gets Anomaly status messages
55    fn get_jidoka_status(&self) -> Vec<String>;
56}
57
58/// A simplified history item for driver results
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct HistoryItem {
61    /// The expression that was evaluated
62    pub expression: String,
63    /// The result as a string
64    pub result: String,
65}
66
67/// TUI Driver implementation
68#[cfg(feature = "tui")]
69pub mod tui_driver {
70    use super::{CalcResult, CalculatorDriver, HistoryItem};
71    use crate::tui::CalculatorApp;
72
73    /// TUI-specific driver wrapping the calculator app
74    #[derive(Debug)]
75    pub struct TuiDriver {
76        app: CalculatorApp,
77    }
78
79    impl Default for TuiDriver {
80        fn default() -> Self {
81            Self::new()
82        }
83    }
84
85    impl TuiDriver {
86        /// Creates a new TUI driver
87        #[must_use]
88        pub fn new() -> Self {
89            Self {
90                app: CalculatorApp::new(),
91            }
92        }
93
94        /// Creates a TUI driver with an existing app
95        #[must_use]
96        pub fn with_app(app: CalculatorApp) -> Self {
97            Self { app }
98        }
99
100        /// Returns a reference to the underlying app
101        #[must_use]
102        pub fn app(&self) -> &CalculatorApp {
103            &self.app
104        }
105
106        /// Returns a mutable reference to the underlying app
107        pub fn app_mut(&mut self) -> &mut CalculatorApp {
108            &mut self.app
109        }
110    }
111
112    impl CalculatorDriver for TuiDriver {
113        fn enter_expression(&mut self, expr: &str) -> CalcResult<()> {
114            self.app.set_input(expr);
115            self.app.evaluate();
116
117            // Check if evaluation produced an error
118            if let Some(Err(e)) = self.app.result() {
119                return Err(e.clone());
120            }
121            Ok(())
122        }
123
124        fn get_result(&self) -> String {
125            self.app.result_display()
126        }
127
128        fn get_input(&self) -> String {
129            self.app.input().to_string()
130        }
131
132        fn clear(&mut self) {
133            self.app.clear();
134        }
135
136        fn get_history(&self) -> Vec<HistoryItem> {
137            self.app
138                .history()
139                .iter_rev()
140                .map(|entry| HistoryItem {
141                    expression: entry.expression.clone(),
142                    result: format!("{}", entry.result),
143                })
144                .collect()
145        }
146
147        fn get_jidoka_status(&self) -> Vec<String> {
148            self.app.jidoka_status()
149        }
150    }
151}
152
153#[cfg(feature = "tui")]
154pub use tui_driver::TuiDriver;
155
156// ===== Unified Test Specifications =====
157// These tests work with ANY CalculatorDriver implementation
158
159/// Verifies basic arithmetic operations
160#[allow(clippy::unwrap_used)]
161pub fn verify_basic_arithmetic<D: CalculatorDriver>(driver: &mut D) {
162    // Addition
163    driver.enter_expression("2 + 3").unwrap();
164    assert_eq!(driver.get_result(), "5");
165    driver.clear();
166
167    // Subtraction
168    driver.enter_expression("10 - 4").unwrap();
169    assert_eq!(driver.get_result(), "6");
170    driver.clear();
171
172    // Multiplication
173    driver.enter_expression("6 * 7").unwrap();
174    assert_eq!(driver.get_result(), "42");
175    driver.clear();
176
177    // Division
178    driver.enter_expression("20 / 4").unwrap();
179    assert_eq!(driver.get_result(), "5");
180    driver.clear();
181}
182
183/// Verifies operator precedence (PEMDAS)
184#[allow(clippy::unwrap_used)]
185pub fn verify_precedence<D: CalculatorDriver>(driver: &mut D) {
186    // Multiplication before addition
187    driver.enter_expression("2 + 3 * 4").unwrap();
188    assert_eq!(driver.get_result(), "14");
189    driver.clear();
190
191    // Power before multiplication
192    driver.enter_expression("2 * 3 ^ 2").unwrap();
193    assert_eq!(driver.get_result(), "18");
194    driver.clear();
195
196    // Parentheses override precedence
197    driver.enter_expression("(2 + 3) * 4").unwrap();
198    assert_eq!(driver.get_result(), "20");
199    driver.clear();
200}
201
202/// Verifies complex nested expressions
203#[allow(clippy::unwrap_used)]
204pub fn verify_complex_expressions<D: CalculatorDriver>(driver: &mut D) {
205    // Showcase expression
206    driver.enter_expression("42 * (3 + 7)").unwrap();
207    assert_eq!(driver.get_result(), "420");
208    driver.clear();
209
210    // Nested parentheses
211    driver.enter_expression("((2 + 3) * (4 + 5))").unwrap();
212    assert_eq!(driver.get_result(), "45");
213    driver.clear();
214
215    // Power with right associativity
216    driver.enter_expression("2 ^ 3 ^ 2").unwrap();
217    assert_eq!(driver.get_result(), "512");
218    driver.clear();
219}
220
221/// Verifies error handling
222pub fn verify_error_handling<D: CalculatorDriver>(driver: &mut D) {
223    // Division by zero
224    let result = driver.enter_expression("1 / 0");
225    assert!(result.is_err());
226    assert!(matches!(result, Err(CalcError::DivisionByZero)));
227    driver.clear();
228
229    // Empty expression (should fail)
230    let _result = driver.enter_expression("");
231    // Empty expressions might be handled differently per implementation
232    driver.clear();
233}
234
235/// Verifies history tracking
236#[allow(clippy::unwrap_used)]
237pub fn verify_history<D: CalculatorDriver>(driver: &mut D) {
238    driver.clear();
239
240    // Perform several calculations
241    driver.enter_expression("1 + 1").unwrap();
242    driver.enter_expression("2 + 2").unwrap();
243    driver.enter_expression("3 + 3").unwrap();
244
245    let history = driver.get_history();
246    assert!(history.len() >= 3);
247
248    // Most recent should be first
249    assert_eq!(history[0].expression, "3 + 3");
250    assert_eq!(history[0].result, "6");
251}
252
253/// Verifies Anomaly status reporting
254#[allow(clippy::unwrap_used)]
255pub fn verify_jidoka_status<D: CalculatorDriver>(driver: &mut D) {
256    driver.clear();
257
258    // After successful calculation
259    driver.enter_expression("5 + 5").unwrap();
260    let status = driver.get_jidoka_status();
261
262    // Should have positive status messages
263    assert!(!status.is_empty());
264    assert!(status
265        .iter()
266        .any(|s| s.contains('✓') || s.contains("satisfied")));
267}
268
269/// Complete verification suite - runs all specifications
270pub fn run_full_specification<D: CalculatorDriver>(driver: &mut D) {
271    verify_basic_arithmetic(driver);
272    verify_precedence(driver);
273    verify_complex_expressions(driver);
274    verify_error_handling(driver);
275    verify_history(driver);
276    verify_jidoka_status(driver);
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282
283    // ===== TUI Driver Tests =====
284
285    #[cfg(feature = "tui")]
286    mod tui_tests {
287        use super::*;
288
289        #[test]
290        fn test_tui_driver_new() {
291            let driver = TuiDriver::new();
292            assert!(driver.get_input().is_empty());
293        }
294
295        #[test]
296        fn test_tui_driver_default() {
297            let driver = TuiDriver::default();
298            assert!(driver.get_input().is_empty());
299        }
300
301        #[test]
302        fn test_tui_driver_with_app() {
303            let app = crate::tui::CalculatorApp::new();
304            let driver = TuiDriver::with_app(app);
305            assert!(driver.get_input().is_empty());
306        }
307
308        #[test]
309        fn test_tui_driver_app_access() {
310            let mut driver = TuiDriver::new();
311            driver.app_mut().set_input("test");
312            assert_eq!(driver.app().input(), "test");
313        }
314
315        #[test]
316        fn test_tui_driver_enter_expression() {
317            let mut driver = TuiDriver::new();
318            driver.enter_expression("2 + 2").unwrap();
319            assert_eq!(driver.get_result(), "4");
320        }
321
322        #[test]
323        fn test_tui_driver_get_input() {
324            let mut driver = TuiDriver::new();
325            driver.enter_expression("5 * 5").unwrap();
326            assert_eq!(driver.get_input(), "5 * 5");
327        }
328
329        #[test]
330        fn test_tui_driver_clear() {
331            let mut driver = TuiDriver::new();
332            driver.enter_expression("1 + 1").unwrap();
333            driver.clear();
334            assert!(driver.get_result().is_empty());
335        }
336
337        #[test]
338        fn test_tui_driver_history() {
339            let mut driver = TuiDriver::new();
340            driver.enter_expression("1 + 1").unwrap();
341            driver.enter_expression("2 + 2").unwrap();
342            let history = driver.get_history();
343            assert_eq!(history.len(), 2);
344        }
345
346        #[test]
347        fn test_tui_driver_jidoka() {
348            let mut driver = TuiDriver::new();
349            driver.enter_expression("10 + 10").unwrap();
350            let status = driver.get_jidoka_status();
351            assert!(!status.is_empty());
352        }
353
354        #[test]
355        fn test_tui_driver_error_handling() {
356            let mut driver = TuiDriver::new();
357            let result = driver.enter_expression("1 / 0");
358            assert!(matches!(result, Err(CalcError::DivisionByZero)));
359        }
360
361        // ===== Unified Specification Tests =====
362
363        #[test]
364        fn test_unified_basic_arithmetic() {
365            let mut driver = TuiDriver::new();
366            verify_basic_arithmetic(&mut driver);
367        }
368
369        #[test]
370        fn test_unified_precedence() {
371            let mut driver = TuiDriver::new();
372            verify_precedence(&mut driver);
373        }
374
375        #[test]
376        fn test_unified_complex_expressions() {
377            let mut driver = TuiDriver::new();
378            verify_complex_expressions(&mut driver);
379        }
380
381        #[test]
382        fn test_unified_error_handling() {
383            let mut driver = TuiDriver::new();
384            verify_error_handling(&mut driver);
385        }
386
387        #[test]
388        fn test_unified_history() {
389            let mut driver = TuiDriver::new();
390            verify_history(&mut driver);
391        }
392
393        #[test]
394        fn test_unified_jidoka_status() {
395            let mut driver = TuiDriver::new();
396            verify_jidoka_status(&mut driver);
397        }
398
399        #[test]
400        fn test_full_specification() {
401            let mut driver = TuiDriver::new();
402            run_full_specification(&mut driver);
403        }
404    }
405
406    // ===== HistoryItem tests =====
407
408    #[test]
409    fn test_history_item_debug() {
410        let item = HistoryItem {
411            expression: "1+1".into(),
412            result: "2".into(),
413        };
414        assert!(format!("{:?}", item).contains("expression"));
415    }
416
417    #[test]
418    fn test_history_item_clone() {
419        let item = HistoryItem {
420            expression: "test".into(),
421            result: "42".into(),
422        };
423        let cloned = item.clone();
424        assert_eq!(item, cloned);
425    }
426}