Skip to main content

aprender_test_showcase/wasm/
driver.rs

1//! WASM Driver - Unified Testing Interface
2//!
3//! This module implements the CalculatorDriver trait for WASM,
4//! enabling the same test specifications to run on both TUI and WASM.
5//!
6//! Probar: Balanced testing - Balanced testing across platforms
7
8use super::calculator::WasmCalculator;
9use super::dom::{DomElement, DomEvent, MockDom};
10use crate::core::{AnomalyValidator, CalcResult};
11use crate::driver::{CalculatorDriver, HistoryItem};
12
13/// WASM Driver wrapping calculator and mock DOM
14#[derive(Debug)]
15pub struct WasmDriver {
16    /// The calculator instance
17    calculator: WasmCalculator,
18    /// Mock DOM for testing
19    dom: MockDom,
20}
21
22impl Default for WasmDriver {
23    fn default() -> Self {
24        Self::new()
25    }
26}
27
28impl WasmDriver {
29    /// Creates a new WASM driver
30    #[must_use]
31    pub fn new() -> Self {
32        Self {
33            calculator: WasmCalculator::new(),
34            dom: MockDom::calculator(),
35        }
36    }
37
38    /// Creates a WASM driver with custom Anomaly validator
39    #[must_use]
40    pub fn with_validator(validator: AnomalyValidator) -> Self {
41        Self {
42            calculator: WasmCalculator::with_validator(validator),
43            dom: MockDom::calculator(),
44        }
45    }
46
47    /// Creates a WASM driver with existing calculator and DOM
48    #[must_use]
49    pub fn with_calculator_and_dom(calculator: WasmCalculator, dom: MockDom) -> Self {
50        Self { calculator, dom }
51    }
52
53    /// Returns a reference to the calculator
54    #[must_use]
55    pub fn calculator(&self) -> &WasmCalculator {
56        &self.calculator
57    }
58
59    /// Returns a mutable reference to the calculator
60    pub fn calculator_mut(&mut self) -> &mut WasmCalculator {
61        &mut self.calculator
62    }
63
64    /// Returns a reference to the DOM
65    #[must_use]
66    pub fn dom(&self) -> &MockDom {
67        &self.dom
68    }
69
70    /// Returns a mutable reference to the DOM
71    pub fn dom_mut(&mut self) -> &mut MockDom {
72        &mut self.dom
73    }
74
75    /// Simulates typing into the input field
76    pub fn type_input(&mut self, text: &str) {
77        self.calculator.set_input(text);
78        self.dom.dispatch_event(DomEvent::input("calc-input", text));
79        self.sync_dom();
80    }
81
82    /// Simulates clicking the equals button
83    pub fn click_equals(&mut self) {
84        self.dom.dispatch_event(DomEvent::click("btn-equals"));
85        let _ = self.calculator.evaluate();
86        self.sync_dom();
87    }
88
89    /// Simulates clicking the clear button
90    pub fn click_clear(&mut self) {
91        self.dom.dispatch_event(DomEvent::click("btn-clear"));
92        self.calculator.clear();
93        self.sync_dom();
94    }
95
96    /// Simulates pressing Enter key
97    pub fn press_enter(&mut self) {
98        self.dom.dispatch_event(DomEvent::key_press("Enter"));
99        let _ = self.calculator.evaluate();
100        self.sync_dom();
101    }
102
103    /// Simulates pressing Escape key
104    pub fn press_escape(&mut self) {
105        self.dom.dispatch_event(DomEvent::key_press("Escape"));
106        self.calculator.clear();
107        self.sync_dom();
108    }
109
110    /// Simulates pressing Backspace key
111    pub fn press_backspace(&mut self) {
112        self.dom.dispatch_event(DomEvent::key_press("Backspace"));
113        self.calculator.backspace();
114        self.sync_dom();
115    }
116
117    /// Synchronizes DOM state with calculator state
118    fn sync_dom(&mut self) {
119        // Update input field
120        self.dom
121            .set_element_text("calc-input", self.calculator.input());
122
123        // Update result display
124        self.dom
125            .set_element_text("calc-result", &self.calculator.result_display());
126
127        // Update status display
128        let status_text = self.calculator.jidoka_status().join("\n");
129        self.dom.set_element_text("calc-status", &status_text);
130
131        // Update history
132        self.dom.clear_children("calc-history");
133        for (i, entry) in self.calculator.history_entries_rev().iter().enumerate() {
134            let item = DomElement::new("li")
135                .with_id(&format!("history-{}", i))
136                .with_text(entry);
137            self.dom.append_child("calc-history", item);
138        }
139    }
140
141    /// Gets the result element's text
142    #[must_use]
143    pub fn result_element_text(&self) -> Option<&str> {
144        self.dom.get_element_text("calc-result")
145    }
146
147    /// Gets the input element's text
148    #[must_use]
149    pub fn input_element_text(&self) -> Option<&str> {
150        self.dom.get_element_text("calc-input")
151    }
152
153    /// Gets the status element's text
154    #[must_use]
155    pub fn status_element_text(&self) -> Option<&str> {
156        self.dom.get_element_text("calc-status")
157    }
158
159    /// Gets history list items
160    #[must_use]
161    pub fn history_list_items(&self) -> Vec<String> {
162        let mut items = Vec::new();
163        let mut i = 0;
164        while let Some(elem) = self.dom.get_element(&format!("history-{}", i)) {
165            items.push(elem.text_content.clone());
166            i += 1;
167        }
168        items
169    }
170}
171
172impl CalculatorDriver for WasmDriver {
173    fn enter_expression(&mut self, expr: &str) -> CalcResult<()> {
174        self.type_input(expr);
175        self.calculator.evaluate()?;
176        self.sync_dom();
177        Ok(())
178    }
179
180    fn get_result(&self) -> String {
181        self.calculator.result_display()
182    }
183
184    fn get_input(&self) -> String {
185        self.calculator.input().to_string()
186    }
187
188    fn clear(&mut self) {
189        self.calculator.clear();
190        self.sync_dom();
191    }
192
193    fn get_history(&self) -> Vec<HistoryItem> {
194        self.calculator
195            .history()
196            .iter_rev()
197            .map(|entry| HistoryItem {
198                expression: entry.expression.clone(),
199                result: format_number(entry.result),
200            })
201            .collect()
202    }
203
204    fn get_jidoka_status(&self) -> Vec<String> {
205        self.calculator.jidoka_status().to_vec()
206    }
207}
208
209/// Formats a number for display (removes trailing zeros)
210fn format_number(n: f64) -> String {
211    if n.fract() == 0.0 {
212        format!("{}", n as i64)
213    } else {
214        let s = format!("{:.10}", n);
215        let s = s.trim_end_matches('0');
216        let s = s.trim_end_matches('.');
217        s.to_string()
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224    use crate::core::CalcError;
225    use crate::driver::{
226        run_full_specification, verify_basic_arithmetic, verify_complex_expressions,
227        verify_error_handling, verify_history, verify_jidoka_status, verify_precedence,
228    };
229
230    // ===== Constructor tests =====
231
232    #[test]
233    fn test_wasm_driver_new() {
234        let driver = WasmDriver::new();
235        assert!(driver.get_input().is_empty());
236        assert!(driver.get_result().is_empty());
237    }
238
239    #[test]
240    fn test_wasm_driver_default() {
241        let driver = WasmDriver::default();
242        assert!(driver.get_input().is_empty());
243    }
244
245    #[test]
246    fn test_wasm_driver_with_validator() {
247        let validator = AnomalyValidator::with_max_magnitude(50.0);
248        let mut driver = WasmDriver::with_validator(validator);
249        let result = driver.enter_expression("10 * 10");
250        assert!(matches!(result, Err(CalcError::AnomalyViolation(_))));
251    }
252
253    #[test]
254    fn test_wasm_driver_with_calculator_and_dom() {
255        let calc = WasmCalculator::new();
256        let dom = MockDom::calculator();
257        let driver = WasmDriver::with_calculator_and_dom(calc, dom);
258        assert!(driver.get_input().is_empty());
259    }
260
261    #[test]
262    fn test_wasm_driver_debug() {
263        let driver = WasmDriver::new();
264        let debug = format!("{:?}", driver);
265        assert!(debug.contains("WasmDriver"));
266    }
267
268    // ===== Access tests =====
269
270    #[test]
271    fn test_calculator_access() {
272        let driver = WasmDriver::new();
273        assert!(driver.calculator().input().is_empty());
274    }
275
276    #[test]
277    fn test_calculator_mut_access() {
278        let mut driver = WasmDriver::new();
279        driver.calculator_mut().set_input("test");
280        assert_eq!(driver.calculator().input(), "test");
281    }
282
283    #[test]
284    fn test_dom_access() {
285        let driver = WasmDriver::new();
286        assert!(driver.dom().get_element("calc-input").is_some());
287    }
288
289    #[test]
290    fn test_dom_mut_access() {
291        let mut driver = WasmDriver::new();
292        driver.dom_mut().set_element_text("calc-result", "42");
293        assert_eq!(driver.dom().get_element_text("calc-result"), Some("42"));
294    }
295
296    // ===== Input simulation tests =====
297
298    #[test]
299    fn test_type_input() {
300        let mut driver = WasmDriver::new();
301        driver.type_input("2 + 2");
302        assert_eq!(driver.get_input(), "2 + 2");
303        assert_eq!(driver.input_element_text(), Some("2 + 2"));
304    }
305
306    #[test]
307    fn test_click_equals() {
308        let mut driver = WasmDriver::new();
309        driver.type_input("3 * 3");
310        driver.click_equals();
311        assert_eq!(driver.get_result(), "9");
312        assert_eq!(driver.result_element_text(), Some("9"));
313    }
314
315    #[test]
316    fn test_click_clear() {
317        let mut driver = WasmDriver::new();
318        driver.type_input("5 + 5");
319        driver.click_equals();
320        driver.click_clear();
321        assert!(driver.get_result().is_empty());
322        assert!(driver.get_input().is_empty());
323    }
324
325    #[test]
326    fn test_press_enter() {
327        let mut driver = WasmDriver::new();
328        driver.type_input("4 * 4");
329        driver.press_enter();
330        assert_eq!(driver.get_result(), "16");
331    }
332
333    #[test]
334    fn test_press_escape() {
335        let mut driver = WasmDriver::new();
336        driver.type_input("7 + 7");
337        driver.click_equals();
338        driver.press_escape();
339        assert!(driver.get_input().is_empty());
340        assert!(driver.get_result().is_empty());
341    }
342
343    #[test]
344    fn test_press_backspace() {
345        let mut driver = WasmDriver::new();
346        driver.type_input("123");
347        driver.press_backspace();
348        assert_eq!(driver.get_input(), "12");
349    }
350
351    // ===== DOM sync tests =====
352
353    #[test]
354    fn test_dom_sync_input() {
355        let mut driver = WasmDriver::new();
356        driver.type_input("test expression");
357        assert_eq!(driver.input_element_text(), Some("test expression"));
358    }
359
360    #[test]
361    fn test_dom_sync_result() {
362        let mut driver = WasmDriver::new();
363        driver.enter_expression("8 + 8").unwrap();
364        assert_eq!(driver.result_element_text(), Some("16"));
365    }
366
367    #[test]
368    fn test_dom_sync_status() {
369        let mut driver = WasmDriver::new();
370        driver.enter_expression("2 + 2").unwrap();
371        let status = driver.status_element_text();
372        assert!(status.is_some());
373        assert!(status.unwrap().contains('✓'));
374    }
375
376    #[test]
377    fn test_dom_sync_history() {
378        let mut driver = WasmDriver::new();
379        driver.enter_expression("1 + 1").unwrap();
380        driver.enter_expression("2 + 2").unwrap();
381        let items = driver.history_list_items();
382        assert_eq!(items.len(), 2);
383        assert!(items[0].contains("2 + 2")); // newest first
384    }
385
386    // ===== CalculatorDriver trait tests =====
387
388    #[test]
389    fn test_enter_expression() {
390        let mut driver = WasmDriver::new();
391        driver.enter_expression("6 * 7").unwrap();
392        assert_eq!(driver.get_result(), "42");
393    }
394
395    #[test]
396    fn test_enter_expression_error() {
397        let mut driver = WasmDriver::new();
398        let result = driver.enter_expression("1 / 0");
399        assert!(matches!(result, Err(CalcError::DivisionByZero)));
400    }
401
402    #[test]
403    fn test_get_result() {
404        let mut driver = WasmDriver::new();
405        driver.enter_expression("10 - 3").unwrap();
406        assert_eq!(driver.get_result(), "7");
407    }
408
409    #[test]
410    fn test_get_input() {
411        let mut driver = WasmDriver::new();
412        driver.type_input("expression");
413        assert_eq!(driver.get_input(), "expression");
414    }
415
416    #[test]
417    fn test_clear() {
418        let mut driver = WasmDriver::new();
419        driver.enter_expression("9 * 9").unwrap();
420        driver.clear();
421        assert!(driver.get_result().is_empty());
422        assert!(driver.get_input().is_empty());
423    }
424
425    #[test]
426    fn test_get_history() {
427        let mut driver = WasmDriver::new();
428        driver.enter_expression("1 + 1").unwrap();
429        driver.enter_expression("2 + 2").unwrap();
430        let history = driver.get_history();
431        assert_eq!(history.len(), 2);
432        assert_eq!(history[0].expression, "2 + 2"); // newest first
433        assert_eq!(history[0].result, "4");
434    }
435
436    #[test]
437    fn test_get_jidoka_status() {
438        let mut driver = WasmDriver::new();
439        driver.enter_expression("5 + 5").unwrap();
440        let status = driver.get_jidoka_status();
441        assert!(!status.is_empty());
442    }
443
444    // ===== Unified specification tests - THE UNIFICATION PROOF =====
445
446    #[test]
447    fn test_unified_basic_arithmetic_wasm() {
448        let mut driver = WasmDriver::new();
449        verify_basic_arithmetic(&mut driver);
450    }
451
452    #[test]
453    fn test_unified_precedence_wasm() {
454        let mut driver = WasmDriver::new();
455        verify_precedence(&mut driver);
456    }
457
458    #[test]
459    fn test_unified_complex_expressions_wasm() {
460        let mut driver = WasmDriver::new();
461        verify_complex_expressions(&mut driver);
462    }
463
464    #[test]
465    fn test_unified_error_handling_wasm() {
466        let mut driver = WasmDriver::new();
467        verify_error_handling(&mut driver);
468    }
469
470    #[test]
471    fn test_unified_history_wasm() {
472        let mut driver = WasmDriver::new();
473        verify_history(&mut driver);
474    }
475
476    #[test]
477    fn test_unified_jidoka_status_wasm() {
478        let mut driver = WasmDriver::new();
479        verify_jidoka_status(&mut driver);
480    }
481
482    #[test]
483    fn test_full_specification_wasm() {
484        let mut driver = WasmDriver::new();
485        run_full_specification(&mut driver);
486    }
487
488    // ===== format_number tests =====
489
490    #[test]
491    fn test_format_number_integer() {
492        assert_eq!(format_number(100.0), "100");
493    }
494
495    #[test]
496    fn test_format_number_decimal() {
497        assert_eq!(format_number(2.5), "2.5");
498    }
499
500    #[test]
501    fn test_format_number_negative() {
502        assert_eq!(format_number(-42.0), "-42");
503    }
504
505    // ===== Event history verification tests =====
506
507    #[test]
508    fn test_event_history_input() {
509        let mut driver = WasmDriver::new();
510        driver.type_input("2 + 2");
511        let events = driver.dom().event_history();
512        assert!(events.iter().any(|e| matches!(e, DomEvent::Input { .. })));
513    }
514
515    #[test]
516    fn test_event_history_click() {
517        let mut driver = WasmDriver::new();
518        driver.type_input("2 + 2");
519        driver.click_equals();
520        let events = driver.dom().event_history();
521        assert!(events.iter().any(|e| matches!(e, DomEvent::Click { .. })));
522    }
523
524    #[test]
525    fn test_event_history_key_press() {
526        let mut driver = WasmDriver::new();
527        driver.type_input("2 + 2");
528        driver.press_enter();
529        let events = driver.dom().event_history();
530        assert!(events
531            .iter()
532            .any(|e| matches!(e, DomEvent::KeyPress { key, .. } if key == "Enter")));
533    }
534}