aprender-test-showcase 0.31.2

100% test coverage calculator showcase demonstrating Probar TUI + WASM testing
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
//! Unified Calculator Driver - The Probar Way
//!
//! This module implements the core unification principle:
//! **Write the test logic once, run it everywhere.**
//!
//! Probar: Balanced testing - Balanced testing across platforms

use crate::core::{CalcError, CalcResult};

/// Abstract driver trait for calculator interactions
///
/// This trait defines the interface that both TUI and WASM drivers implement,
/// enabling unified test specifications that work on any platform.
///
/// # Example
///
/// ```rust,ignore
/// // The abstract test specification
/// async fn verify_calculation<D: CalculatorDriver>(driver: &mut D) {
///     driver.enter_expression("10 * (5 + 5)").await;
///     assert_eq!(driver.get_result().await, "100");
/// }
///
/// // TUI test
/// #[test]
/// fn test_tui() {
///     let mut driver = TuiDriver::new();
///     block_on(verify_calculation(&mut driver));
/// }
///
/// // WASM test
/// #[wasm_bindgen_test]
/// async fn test_wasm() {
///     let mut driver = WasmDriver::new().await;
///     verify_calculation(&mut driver).await;
/// }
/// ```
pub trait CalculatorDriver {
    /// Enters an expression into the calculator
    fn enter_expression(&mut self, expr: &str) -> CalcResult<()>;

    /// Gets the current result display
    fn get_result(&self) -> String;

    /// Gets the current input expression
    fn get_input(&self) -> String;

    /// Clears the calculator state
    fn clear(&mut self);

    /// Gets history entries (newest first)
    fn get_history(&self) -> Vec<HistoryItem>;

    /// Gets Anomaly status messages
    fn get_jidoka_status(&self) -> Vec<String>;
}

/// A simplified history item for driver results
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HistoryItem {
    /// The expression that was evaluated
    pub expression: String,
    /// The result as a string
    pub result: String,
}

/// TUI Driver implementation
#[cfg(feature = "tui")]
pub mod tui_driver {
    use super::{CalcResult, CalculatorDriver, HistoryItem};
    use crate::tui::CalculatorApp;

    /// TUI-specific driver wrapping the calculator app
    #[derive(Debug)]
    pub struct TuiDriver {
        app: CalculatorApp,
    }

    impl Default for TuiDriver {
        fn default() -> Self {
            Self::new()
        }
    }

    impl TuiDriver {
        /// Creates a new TUI driver
        #[must_use]
        pub fn new() -> Self {
            Self {
                app: CalculatorApp::new(),
            }
        }

        /// Creates a TUI driver with an existing app
        #[must_use]
        pub fn with_app(app: CalculatorApp) -> Self {
            Self { app }
        }

        /// Returns a reference to the underlying app
        #[must_use]
        pub fn app(&self) -> &CalculatorApp {
            &self.app
        }

        /// Returns a mutable reference to the underlying app
        pub fn app_mut(&mut self) -> &mut CalculatorApp {
            &mut self.app
        }
    }

    impl CalculatorDriver for TuiDriver {
        fn enter_expression(&mut self, expr: &str) -> CalcResult<()> {
            self.app.set_input(expr);
            self.app.evaluate();

            // Check if evaluation produced an error
            if let Some(Err(e)) = self.app.result() {
                return Err(e.clone());
            }
            Ok(())
        }

        fn get_result(&self) -> String {
            self.app.result_display()
        }

        fn get_input(&self) -> String {
            self.app.input().to_string()
        }

        fn clear(&mut self) {
            self.app.clear();
        }

        fn get_history(&self) -> Vec<HistoryItem> {
            self.app
                .history()
                .iter_rev()
                .map(|entry| HistoryItem {
                    expression: entry.expression.clone(),
                    result: format!("{}", entry.result),
                })
                .collect()
        }

        fn get_jidoka_status(&self) -> Vec<String> {
            self.app.jidoka_status()
        }
    }
}

#[cfg(feature = "tui")]
pub use tui_driver::TuiDriver;

// ===== Unified Test Specifications =====
// These tests work with ANY CalculatorDriver implementation

/// Verifies basic arithmetic operations
#[allow(clippy::unwrap_used)]
pub fn verify_basic_arithmetic<D: CalculatorDriver>(driver: &mut D) {
    // Addition
    driver.enter_expression("2 + 3").unwrap();
    assert_eq!(driver.get_result(), "5");
    driver.clear();

    // Subtraction
    driver.enter_expression("10 - 4").unwrap();
    assert_eq!(driver.get_result(), "6");
    driver.clear();

    // Multiplication
    driver.enter_expression("6 * 7").unwrap();
    assert_eq!(driver.get_result(), "42");
    driver.clear();

    // Division
    driver.enter_expression("20 / 4").unwrap();
    assert_eq!(driver.get_result(), "5");
    driver.clear();
}

/// Verifies operator precedence (PEMDAS)
#[allow(clippy::unwrap_used)]
pub fn verify_precedence<D: CalculatorDriver>(driver: &mut D) {
    // Multiplication before addition
    driver.enter_expression("2 + 3 * 4").unwrap();
    assert_eq!(driver.get_result(), "14");
    driver.clear();

    // Power before multiplication
    driver.enter_expression("2 * 3 ^ 2").unwrap();
    assert_eq!(driver.get_result(), "18");
    driver.clear();

    // Parentheses override precedence
    driver.enter_expression("(2 + 3) * 4").unwrap();
    assert_eq!(driver.get_result(), "20");
    driver.clear();
}

/// Verifies complex nested expressions
#[allow(clippy::unwrap_used)]
pub fn verify_complex_expressions<D: CalculatorDriver>(driver: &mut D) {
    // Showcase expression
    driver.enter_expression("42 * (3 + 7)").unwrap();
    assert_eq!(driver.get_result(), "420");
    driver.clear();

    // Nested parentheses
    driver.enter_expression("((2 + 3) * (4 + 5))").unwrap();
    assert_eq!(driver.get_result(), "45");
    driver.clear();

    // Power with right associativity
    driver.enter_expression("2 ^ 3 ^ 2").unwrap();
    assert_eq!(driver.get_result(), "512");
    driver.clear();
}

/// Verifies error handling
pub fn verify_error_handling<D: CalculatorDriver>(driver: &mut D) {
    // Division by zero
    let result = driver.enter_expression("1 / 0");
    assert!(result.is_err());
    assert!(matches!(result, Err(CalcError::DivisionByZero)));
    driver.clear();

    // Empty expression (should fail)
    let _result = driver.enter_expression("");
    // Empty expressions might be handled differently per implementation
    driver.clear();
}

/// Verifies history tracking
#[allow(clippy::unwrap_used)]
pub fn verify_history<D: CalculatorDriver>(driver: &mut D) {
    driver.clear();

    // Perform several calculations
    driver.enter_expression("1 + 1").unwrap();
    driver.enter_expression("2 + 2").unwrap();
    driver.enter_expression("3 + 3").unwrap();

    let history = driver.get_history();
    assert!(history.len() >= 3);

    // Most recent should be first
    assert_eq!(history[0].expression, "3 + 3");
    assert_eq!(history[0].result, "6");
}

/// Verifies Anomaly status reporting
#[allow(clippy::unwrap_used)]
pub fn verify_jidoka_status<D: CalculatorDriver>(driver: &mut D) {
    driver.clear();

    // After successful calculation
    driver.enter_expression("5 + 5").unwrap();
    let status = driver.get_jidoka_status();

    // Should have positive status messages
    assert!(!status.is_empty());
    assert!(status
        .iter()
        .any(|s| s.contains('✓') || s.contains("satisfied")));
}

/// Complete verification suite - runs all specifications
pub fn run_full_specification<D: CalculatorDriver>(driver: &mut D) {
    verify_basic_arithmetic(driver);
    verify_precedence(driver);
    verify_complex_expressions(driver);
    verify_error_handling(driver);
    verify_history(driver);
    verify_jidoka_status(driver);
}

#[cfg(test)]
mod tests {
    use super::*;

    // ===== TUI Driver Tests =====

    #[cfg(feature = "tui")]
    mod tui_tests {
        use super::*;

        #[test]
        fn test_tui_driver_new() {
            let driver = TuiDriver::new();
            assert!(driver.get_input().is_empty());
        }

        #[test]
        fn test_tui_driver_default() {
            let driver = TuiDriver::default();
            assert!(driver.get_input().is_empty());
        }

        #[test]
        fn test_tui_driver_with_app() {
            let app = crate::tui::CalculatorApp::new();
            let driver = TuiDriver::with_app(app);
            assert!(driver.get_input().is_empty());
        }

        #[test]
        fn test_tui_driver_app_access() {
            let mut driver = TuiDriver::new();
            driver.app_mut().set_input("test");
            assert_eq!(driver.app().input(), "test");
        }

        #[test]
        fn test_tui_driver_enter_expression() {
            let mut driver = TuiDriver::new();
            driver.enter_expression("2 + 2").unwrap();
            assert_eq!(driver.get_result(), "4");
        }

        #[test]
        fn test_tui_driver_get_input() {
            let mut driver = TuiDriver::new();
            driver.enter_expression("5 * 5").unwrap();
            assert_eq!(driver.get_input(), "5 * 5");
        }

        #[test]
        fn test_tui_driver_clear() {
            let mut driver = TuiDriver::new();
            driver.enter_expression("1 + 1").unwrap();
            driver.clear();
            assert!(driver.get_result().is_empty());
        }

        #[test]
        fn test_tui_driver_history() {
            let mut driver = TuiDriver::new();
            driver.enter_expression("1 + 1").unwrap();
            driver.enter_expression("2 + 2").unwrap();
            let history = driver.get_history();
            assert_eq!(history.len(), 2);
        }

        #[test]
        fn test_tui_driver_jidoka() {
            let mut driver = TuiDriver::new();
            driver.enter_expression("10 + 10").unwrap();
            let status = driver.get_jidoka_status();
            assert!(!status.is_empty());
        }

        #[test]
        fn test_tui_driver_error_handling() {
            let mut driver = TuiDriver::new();
            let result = driver.enter_expression("1 / 0");
            assert!(matches!(result, Err(CalcError::DivisionByZero)));
        }

        // ===== Unified Specification Tests =====

        #[test]
        fn test_unified_basic_arithmetic() {
            let mut driver = TuiDriver::new();
            verify_basic_arithmetic(&mut driver);
        }

        #[test]
        fn test_unified_precedence() {
            let mut driver = TuiDriver::new();
            verify_precedence(&mut driver);
        }

        #[test]
        fn test_unified_complex_expressions() {
            let mut driver = TuiDriver::new();
            verify_complex_expressions(&mut driver);
        }

        #[test]
        fn test_unified_error_handling() {
            let mut driver = TuiDriver::new();
            verify_error_handling(&mut driver);
        }

        #[test]
        fn test_unified_history() {
            let mut driver = TuiDriver::new();
            verify_history(&mut driver);
        }

        #[test]
        fn test_unified_jidoka_status() {
            let mut driver = TuiDriver::new();
            verify_jidoka_status(&mut driver);
        }

        #[test]
        fn test_full_specification() {
            let mut driver = TuiDriver::new();
            run_full_specification(&mut driver);
        }
    }

    // ===== HistoryItem tests =====

    #[test]
    fn test_history_item_debug() {
        let item = HistoryItem {
            expression: "1+1".into(),
            result: "2".into(),
        };
        assert!(format!("{:?}", item).contains("expression"));
    }

    #[test]
    fn test_history_item_clone() {
        let item = HistoryItem {
            expression: "test".into(),
            result: "42".into(),
        };
        let cloned = item.clone();
        assert_eq!(item, cloned);
    }
}