1use super::dom::{DomElement, MockDom};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum KeypadAction {
13 Digit(u8),
15 Decimal,
17 Operator(char),
19 Equals,
21 Clear,
23 OpenParen,
25 CloseParen,
27}
28
29impl KeypadAction {
30 #[must_use]
32 pub fn to_char(&self) -> Option<char> {
33 match self {
34 KeypadAction::Digit(d) => char::from_digit(*d as u32, 10),
35 KeypadAction::Decimal => Some('.'),
36 KeypadAction::Operator(op) => Some(*op),
37 KeypadAction::OpenParen => Some('('),
38 KeypadAction::CloseParen => Some(')'),
39 KeypadAction::Equals | KeypadAction::Clear => None,
40 }
41 }
42
43 #[must_use]
45 pub fn label(&self) -> String {
46 match self {
47 KeypadAction::Digit(d) => d.to_string(),
48 KeypadAction::Decimal => ".".to_string(),
49 KeypadAction::Operator(op) => op.to_string(),
50 KeypadAction::Equals => "=".to_string(),
51 KeypadAction::Clear => "C".to_string(),
52 KeypadAction::OpenParen => "(".to_string(),
53 KeypadAction::CloseParen => ")".to_string(),
54 }
55 }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct KeypadButtonDef {
61 pub action: KeypadAction,
63 pub id: String,
65 pub row: usize,
67 pub col: usize,
69}
70
71impl KeypadButtonDef {
72 #[must_use]
74 pub fn new(action: KeypadAction, row: usize, col: usize) -> Self {
75 let id = match action {
76 KeypadAction::Digit(d) => format!("btn-{}", d),
77 KeypadAction::Decimal => "btn-decimal".to_string(),
78 KeypadAction::Operator(op) => format!("btn-{}", op_name(op)),
79 KeypadAction::Equals => "btn-equals".to_string(),
80 KeypadAction::Clear => "btn-clear".to_string(),
81 KeypadAction::OpenParen => "btn-open-paren".to_string(),
82 KeypadAction::CloseParen => "btn-close-paren".to_string(),
83 };
84 Self {
85 action,
86 id,
87 row,
88 col,
89 }
90 }
91}
92
93fn op_name(op: char) -> &'static str {
95 match op {
96 '+' => "plus",
97 '-' => "minus",
98 '*' => "times",
99 '/' => "divide",
100 '^' => "power",
101 '%' => "mod",
102 _ => "op",
103 }
104}
105
106#[derive(Debug, Clone)]
116pub struct WasmKeypad {
117 buttons: Vec<KeypadButtonDef>,
119 cols: usize,
121 rows: usize,
123}
124
125impl Default for WasmKeypad {
126 fn default() -> Self {
127 Self::new()
128 }
129}
130
131impl WasmKeypad {
132 #[must_use]
134 pub fn new() -> Self {
135 let buttons = vec![
136 KeypadButtonDef::new(KeypadAction::Digit(7), 0, 0),
138 KeypadButtonDef::new(KeypadAction::Digit(8), 0, 1),
139 KeypadButtonDef::new(KeypadAction::Digit(9), 0, 2),
140 KeypadButtonDef::new(KeypadAction::Operator('/'), 0, 3),
141 KeypadButtonDef::new(KeypadAction::Digit(4), 1, 0),
143 KeypadButtonDef::new(KeypadAction::Digit(5), 1, 1),
144 KeypadButtonDef::new(KeypadAction::Digit(6), 1, 2),
145 KeypadButtonDef::new(KeypadAction::Operator('*'), 1, 3),
146 KeypadButtonDef::new(KeypadAction::Digit(1), 2, 0),
148 KeypadButtonDef::new(KeypadAction::Digit(2), 2, 1),
149 KeypadButtonDef::new(KeypadAction::Digit(3), 2, 2),
150 KeypadButtonDef::new(KeypadAction::Operator('-'), 2, 3),
151 KeypadButtonDef::new(KeypadAction::Digit(0), 3, 0),
153 KeypadButtonDef::new(KeypadAction::Decimal, 3, 1),
154 KeypadButtonDef::new(KeypadAction::Equals, 3, 2),
155 KeypadButtonDef::new(KeypadAction::Operator('+'), 3, 3),
156 KeypadButtonDef::new(KeypadAction::Clear, 4, 0),
158 KeypadButtonDef::new(KeypadAction::OpenParen, 4, 1),
159 KeypadButtonDef::new(KeypadAction::CloseParen, 4, 2),
160 KeypadButtonDef::new(KeypadAction::Operator('^'), 4, 3),
161 ];
162
163 Self {
164 buttons,
165 cols: 4,
166 rows: 5,
167 }
168 }
169
170 #[must_use]
172 pub fn button_count(&self) -> usize {
173 self.buttons.len()
174 }
175
176 #[must_use]
178 pub fn dimensions(&self) -> (usize, usize) {
179 (self.rows, self.cols)
180 }
181
182 #[must_use]
184 pub fn buttons(&self) -> &[KeypadButtonDef] {
185 &self.buttons
186 }
187
188 #[must_use]
190 pub fn get_button(&self, index: usize) -> Option<&KeypadButtonDef> {
191 self.buttons.get(index)
192 }
193
194 #[must_use]
196 pub fn get_button_at(&self, row: usize, col: usize) -> Option<&KeypadButtonDef> {
197 if row < self.rows && col < self.cols {
198 self.buttons.get(row * self.cols + col)
199 } else {
200 None
201 }
202 }
203
204 #[must_use]
206 pub fn find_button_by_id(&self, id: &str) -> Option<&KeypadButtonDef> {
207 self.buttons.iter().find(|b| b.id == id)
208 }
209
210 #[must_use]
212 pub fn find_button_by_char(&self, ch: char) -> Option<&KeypadButtonDef> {
213 self.buttons.iter().find(|b| b.action.to_char() == Some(ch))
214 }
215
216 pub fn create_dom_elements(&self) -> Vec<DomElement> {
218 self.buttons
219 .iter()
220 .map(|btn| {
221 DomElement::new("button")
222 .with_id(&btn.id)
223 .with_text(&btn.action.label())
224 .with_class("keypad-btn")
225 .with_class(&format!("keypad-row-{}", btn.row))
226 .with_class(&format!("keypad-col-{}", btn.col))
227 .with_attr("data-action", &format!("{:?}", btn.action))
228 })
229 .collect()
230 }
231
232 #[must_use]
234 pub fn create_keypad_element(&self) -> DomElement {
235 let mut keypad = DomElement::new("div")
236 .with_id("calc-keypad")
237 .with_class("keypad");
238
239 for btn_elem in self.create_dom_elements() {
240 keypad = keypad.with_child(btn_elem);
241 }
242
243 keypad
244 }
245
246 #[must_use]
248 pub fn handle_click(&self, element_id: &str) -> Option<KeypadAction> {
249 self.find_button_by_id(element_id).map(|btn| btn.action)
250 }
251
252 #[must_use]
254 pub fn key_to_action(key: &str) -> Option<KeypadAction> {
255 match key {
256 "0" => Some(KeypadAction::Digit(0)),
257 "1" => Some(KeypadAction::Digit(1)),
258 "2" => Some(KeypadAction::Digit(2)),
259 "3" => Some(KeypadAction::Digit(3)),
260 "4" => Some(KeypadAction::Digit(4)),
261 "5" => Some(KeypadAction::Digit(5)),
262 "6" => Some(KeypadAction::Digit(6)),
263 "7" => Some(KeypadAction::Digit(7)),
264 "8" => Some(KeypadAction::Digit(8)),
265 "9" => Some(KeypadAction::Digit(9)),
266 "." => Some(KeypadAction::Decimal),
267 "+" => Some(KeypadAction::Operator('+')),
268 "-" => Some(KeypadAction::Operator('-')),
269 "*" => Some(KeypadAction::Operator('*')),
270 "/" => Some(KeypadAction::Operator('/')),
271 "^" => Some(KeypadAction::Operator('^')),
272 "%" => Some(KeypadAction::Operator('%')),
273 "(" => Some(KeypadAction::OpenParen),
274 ")" => Some(KeypadAction::CloseParen),
275 "Enter" | "=" => Some(KeypadAction::Equals),
276 "Escape" | "c" | "C" => Some(KeypadAction::Clear),
277 _ => None,
278 }
279 }
280}
281
282pub trait MockDomKeypadExt {
284 fn add_keypad(&mut self, keypad: &WasmKeypad);
286}
287
288impl MockDomKeypadExt for MockDom {
289 fn add_keypad(&mut self, keypad: &WasmKeypad) {
290 let keypad_elem = keypad.create_keypad_element();
291
292 self.register_element(keypad_elem);
294
295 for btn_def in keypad.buttons() {
297 let btn_elem = DomElement::new("button")
298 .with_id(&btn_def.id)
299 .with_text(&btn_def.action.label())
300 .with_class("keypad-btn");
301 self.register_element(btn_elem);
302 }
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309
310 #[test]
313 fn test_keypad_action_digit_to_char() {
314 for d in 0..=9 {
315 let action = KeypadAction::Digit(d);
316 assert_eq!(action.to_char(), char::from_digit(d as u32, 10));
317 }
318 }
319
320 #[test]
321 fn test_keypad_action_operator_to_char() {
322 for op in ['+', '-', '*', '/', '^'] {
323 let action = KeypadAction::Operator(op);
324 assert_eq!(action.to_char(), Some(op));
325 }
326 }
327
328 #[test]
329 fn test_keypad_action_decimal_to_char() {
330 assert_eq!(KeypadAction::Decimal.to_char(), Some('.'));
331 }
332
333 #[test]
334 fn test_keypad_action_parens_to_char() {
335 assert_eq!(KeypadAction::OpenParen.to_char(), Some('('));
336 assert_eq!(KeypadAction::CloseParen.to_char(), Some(')'));
337 }
338
339 #[test]
340 fn test_keypad_action_equals_clear_no_char() {
341 assert_eq!(KeypadAction::Equals.to_char(), None);
342 assert_eq!(KeypadAction::Clear.to_char(), None);
343 }
344
345 #[test]
346 fn test_keypad_action_label() {
347 assert_eq!(KeypadAction::Digit(5).label(), "5");
348 assert_eq!(KeypadAction::Decimal.label(), ".");
349 assert_eq!(KeypadAction::Operator('+').label(), "+");
350 assert_eq!(KeypadAction::Equals.label(), "=");
351 assert_eq!(KeypadAction::Clear.label(), "C");
352 assert_eq!(KeypadAction::OpenParen.label(), "(");
353 assert_eq!(KeypadAction::CloseParen.label(), ")");
354 }
355
356 #[test]
357 fn test_keypad_action_copy() {
358 let action = KeypadAction::Digit(5);
359 let copied = action;
360 assert_eq!(action, copied);
361 }
362
363 #[test]
364 fn test_keypad_action_debug() {
365 let action = KeypadAction::Operator('+');
366 let debug = format!("{:?}", action);
367 assert!(debug.contains("Operator"));
368 }
369
370 #[test]
373 fn test_button_def_new_digit() {
374 let btn = KeypadButtonDef::new(KeypadAction::Digit(5), 1, 1);
375 assert_eq!(btn.id, "btn-5");
376 assert_eq!(btn.row, 1);
377 assert_eq!(btn.col, 1);
378 }
379
380 #[test]
381 fn test_button_def_new_operators() {
382 let btn_plus = KeypadButtonDef::new(KeypadAction::Operator('+'), 3, 3);
383 assert_eq!(btn_plus.id, "btn-plus");
384
385 let btn_minus = KeypadButtonDef::new(KeypadAction::Operator('-'), 2, 3);
386 assert_eq!(btn_minus.id, "btn-minus");
387
388 let btn_times = KeypadButtonDef::new(KeypadAction::Operator('*'), 1, 3);
389 assert_eq!(btn_times.id, "btn-times");
390
391 let btn_divide = KeypadButtonDef::new(KeypadAction::Operator('/'), 0, 3);
392 assert_eq!(btn_divide.id, "btn-divide");
393
394 let btn_power = KeypadButtonDef::new(KeypadAction::Operator('^'), 4, 3);
395 assert_eq!(btn_power.id, "btn-power");
396 }
397
398 #[test]
399 fn test_button_def_new_special() {
400 let decimal = KeypadButtonDef::new(KeypadAction::Decimal, 3, 1);
401 assert_eq!(decimal.id, "btn-decimal");
402
403 let equals = KeypadButtonDef::new(KeypadAction::Equals, 3, 2);
404 assert_eq!(equals.id, "btn-equals");
405
406 let clear = KeypadButtonDef::new(KeypadAction::Clear, 4, 0);
407 assert_eq!(clear.id, "btn-clear");
408
409 let open = KeypadButtonDef::new(KeypadAction::OpenParen, 4, 1);
410 assert_eq!(open.id, "btn-open-paren");
411
412 let close = KeypadButtonDef::new(KeypadAction::CloseParen, 4, 2);
413 assert_eq!(close.id, "btn-close-paren");
414 }
415
416 #[test]
417 fn test_button_def_clone() {
418 let btn = KeypadButtonDef::new(KeypadAction::Digit(7), 0, 0);
419 let cloned = btn.clone();
420 assert_eq!(btn, cloned);
421 }
422
423 #[test]
424 fn test_button_def_debug() {
425 let btn = KeypadButtonDef::new(KeypadAction::Digit(9), 0, 2);
426 let debug = format!("{:?}", btn);
427 assert!(debug.contains("KeypadButtonDef"));
428 }
429
430 #[test]
433 fn test_op_name_all() {
434 assert_eq!(op_name('+'), "plus");
435 assert_eq!(op_name('-'), "minus");
436 assert_eq!(op_name('*'), "times");
437 assert_eq!(op_name('/'), "divide");
438 assert_eq!(op_name('^'), "power");
439 assert_eq!(op_name('%'), "mod");
440 assert_eq!(op_name('?'), "op"); }
442
443 #[test]
446 fn test_wasm_keypad_new() {
447 let keypad = WasmKeypad::new();
448 assert_eq!(keypad.button_count(), 20);
449 }
450
451 #[test]
452 fn test_wasm_keypad_default() {
453 let keypad = WasmKeypad::default();
454 assert_eq!(keypad.button_count(), 20);
455 }
456
457 #[test]
458 fn test_wasm_keypad_dimensions() {
459 let keypad = WasmKeypad::new();
460 assert_eq!(keypad.dimensions(), (5, 4));
461 }
462
463 #[test]
464 fn test_wasm_keypad_get_button() {
465 let keypad = WasmKeypad::new();
466 let btn = keypad.get_button(0).unwrap();
467 assert_eq!(btn.action, KeypadAction::Digit(7));
468 }
469
470 #[test]
471 fn test_wasm_keypad_get_button_out_of_bounds() {
472 let keypad = WasmKeypad::new();
473 assert!(keypad.get_button(100).is_none());
474 }
475
476 #[test]
477 fn test_wasm_keypad_get_button_at() {
478 let keypad = WasmKeypad::new();
479 assert_eq!(
481 keypad.get_button_at(0, 0).unwrap().action,
482 KeypadAction::Digit(7)
483 );
484 assert_eq!(
486 keypad.get_button_at(0, 3).unwrap().action,
487 KeypadAction::Operator('/')
488 );
489 assert_eq!(
491 keypad.get_button_at(4, 0).unwrap().action,
492 KeypadAction::Clear
493 );
494 }
495
496 #[test]
497 fn test_wasm_keypad_get_button_at_out_of_bounds() {
498 let keypad = WasmKeypad::new();
499 assert!(keypad.get_button_at(10, 10).is_none());
500 }
501
502 #[test]
503 fn test_wasm_keypad_get_button_at_boundary_row() {
504 let keypad = WasmKeypad::new();
505 assert!(keypad.get_button_at(4, 0).is_some());
507 assert!(keypad.get_button_at(5, 0).is_none());
508 }
509
510 #[test]
511 fn test_wasm_keypad_get_button_at_boundary_col() {
512 let keypad = WasmKeypad::new();
513 assert!(keypad.get_button_at(0, 3).is_some());
515 assert!(keypad.get_button_at(0, 4).is_none());
516 }
517
518 #[test]
519 fn test_wasm_keypad_get_button_at_exact_boundary() {
520 let keypad = WasmKeypad::new();
521 assert!(keypad.get_button_at(5, 0).is_none());
524 assert!(keypad.get_button_at(5, 1).is_none());
525 assert!(keypad.get_button_at(5, 2).is_none());
526 assert!(keypad.get_button_at(5, 3).is_none());
527 }
528
529 #[test]
530 fn test_wasm_keypad_find_by_id() {
531 let keypad = WasmKeypad::new();
532 let btn = keypad.find_button_by_id("btn-5").unwrap();
533 assert_eq!(btn.action, KeypadAction::Digit(5));
534 }
535
536 #[test]
537 fn test_wasm_keypad_find_by_id_not_found() {
538 let keypad = WasmKeypad::new();
539 assert!(keypad.find_button_by_id("nonexistent").is_none());
540 }
541
542 #[test]
543 fn test_wasm_keypad_find_by_char() {
544 let keypad = WasmKeypad::new();
545 let btn = keypad.find_button_by_char('+').unwrap();
546 assert_eq!(btn.action, KeypadAction::Operator('+'));
547 }
548
549 #[test]
550 fn test_wasm_keypad_find_by_char_not_found() {
551 let keypad = WasmKeypad::new();
552 assert!(keypad.find_button_by_char('?').is_none());
553 }
554
555 #[test]
558 fn test_keypad_row_0() {
559 let keypad = WasmKeypad::new();
560 assert_eq!(
561 keypad.get_button_at(0, 0).unwrap().action,
562 KeypadAction::Digit(7)
563 );
564 assert_eq!(
565 keypad.get_button_at(0, 1).unwrap().action,
566 KeypadAction::Digit(8)
567 );
568 assert_eq!(
569 keypad.get_button_at(0, 2).unwrap().action,
570 KeypadAction::Digit(9)
571 );
572 assert_eq!(
573 keypad.get_button_at(0, 3).unwrap().action,
574 KeypadAction::Operator('/')
575 );
576 }
577
578 #[test]
579 fn test_keypad_row_1() {
580 let keypad = WasmKeypad::new();
581 assert_eq!(
582 keypad.get_button_at(1, 0).unwrap().action,
583 KeypadAction::Digit(4)
584 );
585 assert_eq!(
586 keypad.get_button_at(1, 1).unwrap().action,
587 KeypadAction::Digit(5)
588 );
589 assert_eq!(
590 keypad.get_button_at(1, 2).unwrap().action,
591 KeypadAction::Digit(6)
592 );
593 assert_eq!(
594 keypad.get_button_at(1, 3).unwrap().action,
595 KeypadAction::Operator('*')
596 );
597 }
598
599 #[test]
600 fn test_keypad_row_2() {
601 let keypad = WasmKeypad::new();
602 assert_eq!(
603 keypad.get_button_at(2, 0).unwrap().action,
604 KeypadAction::Digit(1)
605 );
606 assert_eq!(
607 keypad.get_button_at(2, 1).unwrap().action,
608 KeypadAction::Digit(2)
609 );
610 assert_eq!(
611 keypad.get_button_at(2, 2).unwrap().action,
612 KeypadAction::Digit(3)
613 );
614 assert_eq!(
615 keypad.get_button_at(2, 3).unwrap().action,
616 KeypadAction::Operator('-')
617 );
618 }
619
620 #[test]
621 fn test_keypad_row_3() {
622 let keypad = WasmKeypad::new();
623 assert_eq!(
624 keypad.get_button_at(3, 0).unwrap().action,
625 KeypadAction::Digit(0)
626 );
627 assert_eq!(
628 keypad.get_button_at(3, 1).unwrap().action,
629 KeypadAction::Decimal
630 );
631 assert_eq!(
632 keypad.get_button_at(3, 2).unwrap().action,
633 KeypadAction::Equals
634 );
635 assert_eq!(
636 keypad.get_button_at(3, 3).unwrap().action,
637 KeypadAction::Operator('+')
638 );
639 }
640
641 #[test]
642 fn test_keypad_row_4() {
643 let keypad = WasmKeypad::new();
644 assert_eq!(
645 keypad.get_button_at(4, 0).unwrap().action,
646 KeypadAction::Clear
647 );
648 assert_eq!(
649 keypad.get_button_at(4, 1).unwrap().action,
650 KeypadAction::OpenParen
651 );
652 assert_eq!(
653 keypad.get_button_at(4, 2).unwrap().action,
654 KeypadAction::CloseParen
655 );
656 assert_eq!(
657 keypad.get_button_at(4, 3).unwrap().action,
658 KeypadAction::Operator('^')
659 );
660 }
661
662 #[test]
665 fn test_create_dom_elements() {
666 let keypad = WasmKeypad::new();
667 let elements = keypad.create_dom_elements();
668 assert_eq!(elements.len(), 20);
669
670 assert_eq!(elements[0].id, "btn-7");
672 assert_eq!(elements[0].tag, "button");
673 assert!(elements[0].has_class("keypad-btn"));
674 }
675
676 #[test]
677 fn test_create_keypad_element() {
678 let keypad = WasmKeypad::new();
679 let elem = keypad.create_keypad_element();
680 assert_eq!(elem.id, "calc-keypad");
681 assert!(elem.has_class("keypad"));
682 assert_eq!(elem.children.len(), 20);
683 }
684
685 #[test]
686 fn test_handle_click_digit() {
687 let keypad = WasmKeypad::new();
688 let action = keypad.handle_click("btn-5");
689 assert_eq!(action, Some(KeypadAction::Digit(5)));
690 }
691
692 #[test]
693 fn test_handle_click_operator() {
694 let keypad = WasmKeypad::new();
695 let action = keypad.handle_click("btn-plus");
696 assert_eq!(action, Some(KeypadAction::Operator('+')));
697 }
698
699 #[test]
700 fn test_handle_click_equals() {
701 let keypad = WasmKeypad::new();
702 let action = keypad.handle_click("btn-equals");
703 assert_eq!(action, Some(KeypadAction::Equals));
704 }
705
706 #[test]
707 fn test_handle_click_clear() {
708 let keypad = WasmKeypad::new();
709 let action = keypad.handle_click("btn-clear");
710 assert_eq!(action, Some(KeypadAction::Clear));
711 }
712
713 #[test]
714 fn test_handle_click_unknown() {
715 let keypad = WasmKeypad::new();
716 let action = keypad.handle_click("nonexistent");
717 assert_eq!(action, None);
718 }
719
720 #[test]
723 fn test_key_to_action_digits() {
724 for d in '0'..='9' {
725 let action = WasmKeypad::key_to_action(&d.to_string());
726 let expected = d.to_digit(10).unwrap() as u8;
727 assert_eq!(action, Some(KeypadAction::Digit(expected)));
728 }
729 }
730
731 #[test]
732 fn test_key_to_action_operators() {
733 assert_eq!(
734 WasmKeypad::key_to_action("+"),
735 Some(KeypadAction::Operator('+'))
736 );
737 assert_eq!(
738 WasmKeypad::key_to_action("-"),
739 Some(KeypadAction::Operator('-'))
740 );
741 assert_eq!(
742 WasmKeypad::key_to_action("*"),
743 Some(KeypadAction::Operator('*'))
744 );
745 assert_eq!(
746 WasmKeypad::key_to_action("/"),
747 Some(KeypadAction::Operator('/'))
748 );
749 assert_eq!(
750 WasmKeypad::key_to_action("^"),
751 Some(KeypadAction::Operator('^'))
752 );
753 assert_eq!(
754 WasmKeypad::key_to_action("%"),
755 Some(KeypadAction::Operator('%'))
756 );
757 }
758
759 #[test]
760 fn test_key_to_action_special() {
761 assert_eq!(WasmKeypad::key_to_action("."), Some(KeypadAction::Decimal));
762 assert_eq!(
763 WasmKeypad::key_to_action("("),
764 Some(KeypadAction::OpenParen)
765 );
766 assert_eq!(
767 WasmKeypad::key_to_action(")"),
768 Some(KeypadAction::CloseParen)
769 );
770 }
771
772 #[test]
773 fn test_key_to_action_equals() {
774 assert_eq!(
775 WasmKeypad::key_to_action("Enter"),
776 Some(KeypadAction::Equals)
777 );
778 assert_eq!(WasmKeypad::key_to_action("="), Some(KeypadAction::Equals));
779 }
780
781 #[test]
782 fn test_key_to_action_clear() {
783 assert_eq!(
784 WasmKeypad::key_to_action("Escape"),
785 Some(KeypadAction::Clear)
786 );
787 assert_eq!(WasmKeypad::key_to_action("c"), Some(KeypadAction::Clear));
788 assert_eq!(WasmKeypad::key_to_action("C"), Some(KeypadAction::Clear));
789 }
790
791 #[test]
792 fn test_key_to_action_unknown() {
793 assert_eq!(WasmKeypad::key_to_action("x"), None);
794 assert_eq!(WasmKeypad::key_to_action("Shift"), None);
795 }
796
797 #[test]
800 fn test_mock_dom_add_keypad() {
801 let mut dom = MockDom::calculator();
802 let keypad = WasmKeypad::new();
803 dom.add_keypad(&keypad);
804
805 assert!(dom.get_element("calc-keypad").is_some());
807
808 assert!(dom.get_element("btn-5").is_some());
810 assert!(dom.get_element("btn-plus").is_some());
811 assert!(dom.get_element("btn-equals").is_some());
812 assert!(dom.get_element("btn-clear").is_some());
813 }
814
815 #[test]
816 fn test_keypad_clone() {
817 let keypad = WasmKeypad::new();
818 let cloned = keypad.clone();
819 assert_eq!(keypad.button_count(), cloned.button_count());
820 }
821
822 #[test]
823 fn test_keypad_debug() {
824 let keypad = WasmKeypad::new();
825 let debug = format!("{:?}", keypad);
826 assert!(debug.contains("WasmKeypad"));
827 }
828
829 #[test]
832 fn prop_all_digits_have_buttons() {
833 let keypad = WasmKeypad::new();
834 for d in 0..=9 {
835 let ch = char::from_digit(d, 10).unwrap();
836 assert!(
837 keypad.find_button_by_char(ch).is_some(),
838 "Missing button for digit {d}"
839 );
840 }
841 }
842
843 #[test]
844 fn prop_all_operators_have_buttons() {
845 let keypad = WasmKeypad::new();
846 for op in ['+', '-', '*', '/', '^'] {
847 assert!(
848 keypad.find_button_by_char(op).is_some(),
849 "Missing button for operator {op}"
850 );
851 }
852 }
853
854 #[test]
855 fn prop_button_positions_unique() {
856 let keypad = WasmKeypad::new();
857 let mut positions = std::collections::HashSet::new();
858 for btn in keypad.buttons() {
859 let pos = (btn.row, btn.col);
860 assert!(positions.insert(pos), "Duplicate position {:?}", pos);
861 }
862 }
863
864 #[test]
865 fn prop_button_ids_unique() {
866 let keypad = WasmKeypad::new();
867 let mut ids = std::collections::HashSet::new();
868 for btn in keypad.buttons() {
869 assert!(ids.insert(btn.id.clone()), "Duplicate ID {}", btn.id);
870 }
871 }
872}