""" Test for MposKeyboard mode switching crash. This test reproduces the crash that occurs when clicking the UP arrow to switch to uppercase mode in MposKeyboard. Usage: Desktop: ./tests/unittest.sh tests/test_graphical_keyboard_mode_switch.py Device: ./tests/unittest.sh tests/test_graphical_keyboard_mode_switch.py --ondevice """ import unittest import lvgl as lv from mpos import MposKeyboard, wait_for_render class TestKeyboardModeSwitch(unittest.TestCase): """Test keyboard mode switching doesn't crash.""" def setUp(self): """Set up test fixtures.""" self.screen = lv.obj() self.screen.set_size(320, 240) # Create textarea self.textarea = lv.textarea(self.screen) self.textarea.set_size(280, 40) self.textarea.align(lv.ALIGN.TOP_MID, 0, 10) self.textarea.set_one_line(True) # Load screen lv.screen_load(self.screen) wait_for_render(5) def tearDown(self): """Clean up.""" lv.screen_load(lv.obj()) wait_for_render(5) def test_switch_to_uppercase_with_symbol_up(self): """ Test switching to uppercase mode. This reproduces the crash that occurred when clicking the UP arrow button. The bug was that set_mode() was called without set_map() first. """ print("\n=== Testing uppercase mode switch ===") # Create keyboard keyboard = MposKeyboard(self.screen) keyboard.set_textarea(self.textarea) keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0) wait_for_render(10) # Keyboard starts in lowercase mode print("Initial mode: MODE_LOWERCASE") # Find the UP symbol button by searching all buttons up_button_index = None for i in range(100): # Try up to 100 buttons try: text = keyboard.get_button_text(i) if text == lv.SYMBOL.UP: up_button_index = i print(f"Found UP symbol at button index {i}") break except: pass self.assertIsNotNone(up_button_index, "Should find UP symbol button") # Test mode switching (this is what happens when the user clicks UP) print("Switching to uppercase mode...") try: keyboard.set_mode(MposKeyboard.MODE_UPPERCASE) wait_for_render(5) print("SUCCESS: No crash when switching to uppercase!") # Verify we're now in uppercase mode by checking the button changed down_button_text = keyboard.get_button_text(up_button_index) print(f"After switch, button {up_button_index} text: {down_button_text}") self.assertEqual(down_button_text, lv.SYMBOL.DOWN, "Should show DOWN symbol in uppercase mode") # Switch back to lowercase keyboard.set_mode(MposKeyboard.MODE_LOWERCASE) wait_for_render(5) up_button_text = keyboard.get_button_text(up_button_index) self.assertEqual(up_button_text, lv.SYMBOL.UP, "Should show UP symbol in lowercase mode") except Exception as e: self.fail(f"CRASH: Switching to uppercase caused exception: {e}") def test_switch_modes_multiple_times(self): """ Test switching between all keyboard modes multiple times. Tests the full mode switching cycle to ensure all modes work. """ print("\n=== Testing multiple mode switches ===") keyboard = MposKeyboard(self.screen) keyboard.set_textarea(self.textarea) keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0) wait_for_render(10) modes_to_test = [ (MposKeyboard.MODE_UPPERCASE, "MODE_UPPERCASE"), (MposKeyboard.MODE_LOWERCASE, "MODE_LOWERCASE"), (MposKeyboard.MODE_NUMBERS, "MODE_NUMBERS"), (MposKeyboard.MODE_SPECIALS, "MODE_SPECIALS"), (MposKeyboard.MODE_LOWERCASE, "MODE_LOWERCASE (again)"), ] for mode, mode_name in modes_to_test: print(f"Switching to {mode_name}...") try: keyboard.set_mode(mode) wait_for_render(5) print(f" SUCCESS: Switched to {mode_name}") except Exception as e: self.fail(f" CRASH: Switching to {mode_name} caused exception: {e}") def test_event_handler_exists(self): """ Verify that the event handler exists and is properly connected. The _handle_events method should filter events to only process VALUE_CHANGED events. This prevents duplicate characters from being typed when other events (like PRESSED, RELEASED, etc.) are fired. The fix ensures: 1. Only VALUE_CHANGED events are processed 2. None/invalid button text is ignored 3. Each button press results in exactly ONE character being added """ print("\n=== Verifying event handler exists ===") keyboard = MposKeyboard(self.screen) keyboard.set_textarea(self.textarea) keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0) wait_for_render(10) # Verify the event handler method exists and is callable self.assertTrue(hasattr(keyboard, '_handle_events'), "Keyboard should have _handle_events method") self.assertTrue(callable(keyboard._handle_events), "_handle_events should be callable") print("SUCCESS: Event handler exists and is properly set up") print("Note: The handler filters for VALUE_CHANGED events only")