aprender_test_showcase/wasm/
driver.rs1use super::calculator::WasmCalculator;
9use super::dom::{DomElement, DomEvent, MockDom};
10use crate::core::{AnomalyValidator, CalcResult};
11use crate::driver::{CalculatorDriver, HistoryItem};
12
13#[derive(Debug)]
15pub struct WasmDriver {
16 calculator: WasmCalculator,
18 dom: MockDom,
20}
21
22impl Default for WasmDriver {
23 fn default() -> Self {
24 Self::new()
25 }
26}
27
28impl WasmDriver {
29 #[must_use]
31 pub fn new() -> Self {
32 Self {
33 calculator: WasmCalculator::new(),
34 dom: MockDom::calculator(),
35 }
36 }
37
38 #[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 #[must_use]
49 pub fn with_calculator_and_dom(calculator: WasmCalculator, dom: MockDom) -> Self {
50 Self { calculator, dom }
51 }
52
53 #[must_use]
55 pub fn calculator(&self) -> &WasmCalculator {
56 &self.calculator
57 }
58
59 pub fn calculator_mut(&mut self) -> &mut WasmCalculator {
61 &mut self.calculator
62 }
63
64 #[must_use]
66 pub fn dom(&self) -> &MockDom {
67 &self.dom
68 }
69
70 pub fn dom_mut(&mut self) -> &mut MockDom {
72 &mut self.dom
73 }
74
75 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 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 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 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 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 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 fn sync_dom(&mut self) {
119 self.dom
121 .set_element_text("calc-input", self.calculator.input());
122
123 self.dom
125 .set_element_text("calc-result", &self.calculator.result_display());
126
127 let status_text = self.calculator.jidoka_status().join("\n");
129 self.dom.set_element_text("calc-status", &status_text);
130
131 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 #[must_use]
143 pub fn result_element_text(&self) -> Option<&str> {
144 self.dom.get_element_text("calc-result")
145 }
146
147 #[must_use]
149 pub fn input_element_text(&self) -> Option<&str> {
150 self.dom.get_element_text("calc-input")
151 }
152
153 #[must_use]
155 pub fn status_element_text(&self) -> Option<&str> {
156 self.dom.get_element_text("calc-status")
157 }
158
159 #[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
209fn 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 #[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 #[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 #[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 #[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")); }
385
386 #[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"); 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 #[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 #[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 #[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}