#![allow(clippy::unwrap_used)]
#![allow(clippy::disallowed_methods)]
use proptest::prelude::*;
use showcase_calculator::wasm::{KeypadAction, WasmKeypad};
fn digit_strategy() -> impl Strategy<Value = u8> {
0u8..=9u8
}
fn operator_strategy() -> impl Strategy<Value = char> {
prop_oneof![
Just('+'),
Just('-'),
Just('*'),
Just('/'),
Just('^'),
Just('%')
]
}
fn keypad_action_strategy() -> impl Strategy<Value = KeypadAction> {
prop_oneof![
digit_strategy().prop_map(KeypadAction::Digit),
Just(KeypadAction::Decimal),
operator_strategy().prop_map(KeypadAction::Operator),
Just(KeypadAction::Equals),
Just(KeypadAction::Clear),
Just(KeypadAction::OpenParen),
Just(KeypadAction::CloseParen),
]
}
fn grid_position_strategy() -> impl Strategy<Value = (usize, usize)> {
(0usize..5usize, 0usize..4usize)
}
proptest! {
#[test]
fn prop_digit_action_to_char(d in digit_strategy()) {
let action = KeypadAction::Digit(d);
let ch = action.to_char();
prop_assert!(ch.is_some());
prop_assert_eq!(ch.unwrap().to_digit(10), Some(d as u32));
}
#[test]
fn prop_operator_action_to_char(op in operator_strategy()) {
let action = KeypadAction::Operator(op);
prop_assert_eq!(action.to_char(), Some(op));
}
#[test]
fn prop_action_has_label(action in keypad_action_strategy()) {
let label = action.label();
prop_assert!(!label.is_empty());
}
#[test]
fn prop_action_copy_identity(action in keypad_action_strategy()) {
let copied = action;
prop_assert_eq!(action, copied);
}
}
proptest! {
#[test]
fn prop_button_at_valid_position_exists((row, col) in grid_position_strategy()) {
let keypad = WasmKeypad::new();
let button = keypad.get_button_at(row, col);
prop_assert!(button.is_some());
}
#[test]
fn prop_button_at_invalid_row_missing(row in 5usize..100usize, col in 0usize..4usize) {
let keypad = WasmKeypad::new();
prop_assert!(keypad.get_button_at(row, col).is_none());
}
#[test]
fn prop_button_at_invalid_col_missing(row in 0usize..5usize, col in 4usize..100usize) {
let keypad = WasmKeypad::new();
prop_assert!(keypad.get_button_at(row, col).is_none());
}
#[test]
fn prop_all_buttons_have_unique_ids(_seed in any::<u32>()) {
let keypad = WasmKeypad::new();
let mut ids = std::collections::HashSet::new();
for btn in keypad.buttons() {
prop_assert!(ids.insert(btn.id.clone()), "Duplicate ID: {}", btn.id);
}
}
#[test]
fn prop_all_buttons_have_unique_positions(_seed in any::<u32>()) {
let keypad = WasmKeypad::new();
let mut positions = std::collections::HashSet::new();
for btn in keypad.buttons() {
let pos = (btn.row, btn.col);
prop_assert!(positions.insert(pos), "Duplicate position: {:?}", pos);
}
}
#[test]
fn prop_button_id_position_consistency((row, col) in grid_position_strategy()) {
let keypad = WasmKeypad::new();
if let Some(btn) = keypad.get_button_at(row, col) {
let found = keypad.find_button_by_id(&btn.id);
prop_assert!(found.is_some());
prop_assert_eq!(found.unwrap().row, row);
prop_assert_eq!(found.unwrap().col, col);
}
}
#[test]
fn prop_all_digits_findable(d in digit_strategy()) {
let keypad = WasmKeypad::new();
let ch = char::from_digit(d as u32, 10).unwrap();
prop_assert!(keypad.find_button_by_char(ch).is_some());
}
#[test]
fn prop_all_operators_findable(op in operator_strategy()) {
let keypad = WasmKeypad::new();
if op != '%' {
prop_assert!(keypad.find_button_by_char(op).is_some());
}
}
}
proptest! {
#[test]
fn prop_digit_keys_map_to_digit_actions(d in digit_strategy()) {
let key = d.to_string();
let action = WasmKeypad::key_to_action(&key);
prop_assert_eq!(action, Some(KeypadAction::Digit(d)));
}
#[test]
fn prop_operator_keys_map_to_operator_actions(op in operator_strategy()) {
let key = op.to_string();
let action = WasmKeypad::key_to_action(&key);
prop_assert_eq!(action, Some(KeypadAction::Operator(op)));
}
#[test]
fn prop_unknown_keys_map_to_none(key in "[a-zA-Z]{2,10}") {
let action = WasmKeypad::key_to_action(&key);
if key != "Enter" && key != "Escape" {
prop_assert!(action.is_none() || matches!(action, Some(KeypadAction::Clear)));
}
}
}
proptest! {
#[test]
fn prop_all_dom_elements_have_class(_seed in any::<u32>()) {
let keypad = WasmKeypad::new();
let elements = keypad.create_dom_elements();
for elem in elements {
prop_assert!(elem.has_class("keypad-btn"));
}
}
#[test]
fn prop_all_dom_elements_are_buttons(_seed in any::<u32>()) {
let keypad = WasmKeypad::new();
let elements = keypad.create_dom_elements();
for elem in elements {
prop_assert_eq!(elem.tag, "button");
}
}
#[test]
fn prop_keypad_element_has_20_children(_seed in any::<u32>()) {
let keypad = WasmKeypad::new();
let elem = keypad.create_keypad_element();
prop_assert_eq!(elem.children.len(), 20);
}
}
#[test]
fn invariant_keypad_always_has_20_buttons() {
let keypad = WasmKeypad::new();
assert_eq!(keypad.button_count(), 20);
}
#[test]
fn invariant_keypad_always_5_by_4() {
let keypad = WasmKeypad::new();
assert_eq!(keypad.dimensions(), (5, 4));
}
#[test]
fn invariant_keypad_has_all_digits() {
let keypad = WasmKeypad::new();
for d in 0..=9 {
let ch = char::from_digit(d, 10).unwrap();
assert!(
keypad.find_button_by_char(ch).is_some(),
"Missing digit {d}"
);
}
}
#[test]
fn invariant_keypad_has_required_operators() {
let keypad = WasmKeypad::new();
for op in ['+', '-', '*', '/', '^'] {
assert!(
keypad.find_button_by_char(op).is_some(),
"Missing operator {op}"
);
}
}
#[test]
fn invariant_keypad_has_special_buttons() {
let keypad = WasmKeypad::new();
assert!(keypad.find_button_by_id("btn-equals").is_some());
assert!(keypad.find_button_by_id("btn-clear").is_some());
assert!(keypad.find_button_by_id("btn-decimal").is_some());
assert!(keypad.find_button_by_id("btn-open-paren").is_some());
assert!(keypad.find_button_by_id("btn-close-paren").is_some());
}