2025-11-16 19:32:07 +01:00
|
|
|
"""
|
|
|
|
|
Test for keyboard layout switching bug.
|
|
|
|
|
|
2025-11-17 12:27:08 +01:00
|
|
|
This test reproduces the issue where clicking the "Abc" button in numbers mode
|
2025-11-16 19:32:07 +01:00
|
|
|
goes to the wrong (default LVGL) keyboard layout instead of our custom lowercase layout.
|
|
|
|
|
|
|
|
|
|
Usage:
|
|
|
|
|
Desktop: ./tests/unittest.sh tests/test_graphical_keyboard_layout_switching.py
|
|
|
|
|
Device: ./tests/unittest.sh tests/test_graphical_keyboard_layout_switching.py --ondevice
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import unittest
|
|
|
|
|
import lvgl as lv
|
2026-01-13 00:38:17 +01:00
|
|
|
from mpos import MposKeyboard, wait_for_render
|
2025-11-16 19:32:07 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestKeyboardLayoutSwitching(unittest.TestCase):
|
|
|
|
|
"""Test keyboard layout switching between different modes."""
|
|
|
|
|
|
|
|
|
|
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_abc_button_from_numbers_mode(self):
|
|
|
|
|
"""
|
2025-11-17 12:27:08 +01:00
|
|
|
Test that clicking "Abc" button in numbers mode goes to lowercase mode.
|
2025-11-16 19:32:07 +01:00
|
|
|
|
|
|
|
|
BUG: Currently goes to the wrong (default LVGL) keyboard layout
|
|
|
|
|
instead of our custom lowercase layout.
|
|
|
|
|
|
|
|
|
|
Expected behavior:
|
|
|
|
|
1. Start in lowercase mode (has "q", "w", "e", etc.)
|
2025-11-17 12:27:08 +01:00
|
|
|
2. Switch to numbers mode (has "1", "2", "3", etc. and "Abc" button)
|
|
|
|
|
3. Click "Abc" button
|
2025-11-16 19:32:07 +01:00
|
|
|
4. Should return to lowercase mode (has "q", "w", "e", etc.)
|
|
|
|
|
"""
|
2025-11-17 12:27:08 +01:00
|
|
|
print("\n=== Testing 'Abc' button from numbers mode ===")
|
2025-11-16 19:32:07 +01:00
|
|
|
|
|
|
|
|
# Create keyboard
|
|
|
|
|
keyboard = MposKeyboard(self.screen)
|
|
|
|
|
keyboard.set_textarea(self.textarea)
|
|
|
|
|
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
|
|
|
|
wait_for_render(10)
|
|
|
|
|
|
|
|
|
|
# Verify we start in lowercase mode
|
|
|
|
|
print("Step 1: Verify initial lowercase mode")
|
|
|
|
|
# Find 'q' button (should be in lowercase layout)
|
|
|
|
|
q_button_index = None
|
|
|
|
|
for i in range(100):
|
|
|
|
|
try:
|
|
|
|
|
text = keyboard.get_button_text(i)
|
|
|
|
|
if text == "q":
|
|
|
|
|
q_button_index = i
|
|
|
|
|
print(f" Found 'q' at index {i} - GOOD (lowercase mode)")
|
|
|
|
|
break
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
self.assertIsNotNone(q_button_index, "Should find 'q' in lowercase mode")
|
|
|
|
|
|
|
|
|
|
# Switch to numbers mode
|
|
|
|
|
print("\nStep 2: Switch to numbers mode")
|
|
|
|
|
keyboard.set_mode(MposKeyboard.MODE_NUMBERS)
|
|
|
|
|
wait_for_render(5)
|
|
|
|
|
|
|
|
|
|
# Verify we're in numbers mode by finding '1' button
|
|
|
|
|
one_button_index = None
|
|
|
|
|
for i in range(100):
|
|
|
|
|
try:
|
|
|
|
|
text = keyboard.get_button_text(i)
|
|
|
|
|
if text == "1":
|
|
|
|
|
one_button_index = i
|
|
|
|
|
print(f" Found '1' at index {i} - GOOD (numbers mode)")
|
|
|
|
|
break
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
self.assertIsNotNone(one_button_index, "Should find '1' in numbers mode")
|
|
|
|
|
|
2025-11-17 12:27:08 +01:00
|
|
|
# Find the 'Abc' button in numbers mode
|
|
|
|
|
print("\nStep 3: Find 'Abc' button in numbers mode")
|
2025-11-16 19:32:07 +01:00
|
|
|
abc_button_index = None
|
|
|
|
|
for i in range(100):
|
|
|
|
|
try:
|
|
|
|
|
text = keyboard.get_button_text(i)
|
2025-11-17 12:27:08 +01:00
|
|
|
if text == "Abc":
|
2025-11-16 19:32:07 +01:00
|
|
|
abc_button_index = i
|
2025-11-17 12:27:08 +01:00
|
|
|
print(f" Found 'Abc' at index {i}")
|
2025-11-16 19:32:07 +01:00
|
|
|
break
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
|
2025-11-17 12:27:08 +01:00
|
|
|
self.assertIsNotNone(abc_button_index, "Should find 'Abc' button in numbers mode")
|
2025-11-16 19:32:07 +01:00
|
|
|
|
2025-11-17 12:27:08 +01:00
|
|
|
# Switch back to lowercase by calling set_mode (simulating clicking 'Abc')
|
|
|
|
|
print("\nStep 4: Click 'Abc' to switch back to lowercase")
|
2025-11-16 19:32:07 +01:00
|
|
|
keyboard.set_mode(MposKeyboard.MODE_LOWERCASE)
|
|
|
|
|
wait_for_render(5)
|
|
|
|
|
|
|
|
|
|
# Verify we're back in lowercase mode using DISTINGUISHING LABELS
|
|
|
|
|
# When in LOWERCASE mode:
|
|
|
|
|
# - Our custom keyboard has "?123" (to switch to numbers)
|
|
|
|
|
# - Default LVGL keyboard has "1#" (to switch to numbers) and "ABC" (to switch to uppercase)
|
|
|
|
|
#
|
2025-11-17 12:27:08 +01:00
|
|
|
# Note: "Abc" only appears in NUMBERS/SPECIALS modes to switch back to lowercase
|
2025-11-16 19:32:07 +01:00
|
|
|
print("\nStep 5: Verify we're in OUR custom lowercase mode (not default LVGL)")
|
|
|
|
|
|
|
|
|
|
found_labels = {}
|
|
|
|
|
for i in range(100):
|
|
|
|
|
try:
|
|
|
|
|
text = keyboard.get_button_text(i)
|
|
|
|
|
# Check for all possible distinguishing labels
|
2025-11-17 12:27:08 +01:00
|
|
|
if text in ["Abc", "ABC", "?123", "1#", lv.SYMBOL.UP, lv.SYMBOL.DOWN]:
|
2025-11-16 19:32:07 +01:00
|
|
|
found_labels[text] = i
|
|
|
|
|
print(f" Found label '{text}' at index {i}")
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
# Check for WRONG labels (default LVGL keyboard in lowercase mode)
|
|
|
|
|
if "ABC" in found_labels:
|
|
|
|
|
print(f" ERROR: Found 'ABC' - this is the DEFAULT LVGL keyboard!")
|
|
|
|
|
self.fail("BUG DETECTED: Got default LVGL lowercase keyboard with 'ABC' label instead of custom keyboard")
|
|
|
|
|
|
|
|
|
|
if "1#" in found_labels:
|
|
|
|
|
print(f" ERROR: Found '1#' - this is the DEFAULT LVGL keyboard!")
|
|
|
|
|
self.fail("BUG DETECTED: Got default LVGL lowercase keyboard with '1#' label instead of custom keyboard with '?123'")
|
|
|
|
|
|
|
|
|
|
# Check for CORRECT labels (our custom lowercase keyboard)
|
|
|
|
|
if "?123" not in found_labels:
|
|
|
|
|
print(f" ERROR: Did not find '?123' - should be in custom lowercase layout!")
|
|
|
|
|
print(f" Found labels: {list(found_labels.keys())}")
|
|
|
|
|
self.fail("BUG: Should find '?123' label in custom lowercase mode, but got: " + str(list(found_labels.keys())))
|
|
|
|
|
|
|
|
|
|
# Also verify we have the UP symbol (our custom keyboard) not ABC (default)
|
|
|
|
|
if lv.SYMBOL.UP not in found_labels:
|
|
|
|
|
print(f" ERROR: Did not find UP symbol - should be in custom lowercase layout!")
|
|
|
|
|
print(f" Found labels: {list(found_labels.keys())}")
|
|
|
|
|
self.fail("BUG: Should find UP symbol in custom lowercase mode")
|
|
|
|
|
|
|
|
|
|
print(f" Found '?123' at index {found_labels['?123']} - GOOD (custom keyboard)")
|
|
|
|
|
print(f" Found UP symbol at index {found_labels[lv.SYMBOL.UP]} - GOOD (custom keyboard)")
|
2025-11-17 12:27:08 +01:00
|
|
|
print("\nSUCCESS: 'Abc' button correctly returns to custom lowercase layout!")
|
2025-11-16 19:32:07 +01:00
|
|
|
|
|
|
|
|
def test_layout_switching_cycle(self):
|
|
|
|
|
"""
|
|
|
|
|
Test full cycle of layout switching: lowercase -> numbers -> specials -> lowercase.
|
|
|
|
|
|
|
|
|
|
This ensures all mode switches preserve our custom layouts.
|
|
|
|
|
"""
|
|
|
|
|
print("\n=== Testing full layout switching cycle ===")
|
|
|
|
|
|
|
|
|
|
keyboard = MposKeyboard(self.screen)
|
|
|
|
|
keyboard.set_textarea(self.textarea)
|
|
|
|
|
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
|
|
|
|
wait_for_render(10)
|
|
|
|
|
|
|
|
|
|
# Define what we expect to find in each mode
|
|
|
|
|
mode_tests = [
|
|
|
|
|
(MposKeyboard.MODE_LOWERCASE, "q", "lowercase"),
|
|
|
|
|
(MposKeyboard.MODE_NUMBERS, "1", "numbers"),
|
|
|
|
|
(MposKeyboard.MODE_SPECIALS, "~", "specials"),
|
|
|
|
|
(MposKeyboard.MODE_LOWERCASE, "q", "lowercase (again)"),
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for mode, expected_key, mode_name in mode_tests:
|
|
|
|
|
print(f"\nSwitching to {mode_name}...")
|
|
|
|
|
keyboard.set_mode(mode)
|
|
|
|
|
wait_for_render(5)
|
|
|
|
|
|
|
|
|
|
# Find the expected key
|
|
|
|
|
found = False
|
|
|
|
|
for i in range(100):
|
|
|
|
|
try:
|
|
|
|
|
text = keyboard.get_button_text(i)
|
|
|
|
|
if text == expected_key:
|
|
|
|
|
print(f" Found '{expected_key}' at index {i} - GOOD")
|
|
|
|
|
found = True
|
|
|
|
|
break
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
self.assertTrue(found,
|
|
|
|
|
f"Should find '{expected_key}' in {mode_name} mode")
|
|
|
|
|
|
|
|
|
|
print("\nSUCCESS: All layout switches preserve custom layouts!")
|
|
|
|
|
|
|
|
|
|
def test_event_handler_switches_layout(self):
|
|
|
|
|
"""
|
|
|
|
|
Test that the event handler properly switches layouts.
|
|
|
|
|
|
2025-11-17 12:27:08 +01:00
|
|
|
This simulates what happens when the user actually CLICKS the "Abc" button,
|
2025-11-16 19:32:07 +01:00
|
|
|
going through the _handle_events method instead of calling set_mode() directly.
|
|
|
|
|
"""
|
|
|
|
|
print("\n=== Testing event handler layout switching ===")
|
|
|
|
|
|
|
|
|
|
keyboard = MposKeyboard(self.screen)
|
|
|
|
|
keyboard.set_textarea(self.textarea)
|
|
|
|
|
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
|
|
|
|
wait_for_render(10)
|
|
|
|
|
|
|
|
|
|
# Switch to numbers mode first
|
|
|
|
|
print("Step 1: Switch to numbers mode")
|
|
|
|
|
keyboard.set_mode(MposKeyboard.MODE_NUMBERS)
|
|
|
|
|
wait_for_render(5)
|
|
|
|
|
|
|
|
|
|
# Verify we're in numbers mode
|
|
|
|
|
one_found = False
|
|
|
|
|
for i in range(100):
|
|
|
|
|
try:
|
|
|
|
|
if keyboard.get_button_text(i) == "1":
|
|
|
|
|
one_found = True
|
|
|
|
|
print(f" Found '1' - in numbers mode")
|
|
|
|
|
break
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
self.assertTrue(one_found, "Should be in numbers mode")
|
|
|
|
|
|
2025-11-17 12:27:08 +01:00
|
|
|
# Now simulate what the event handler does when "Qbc" is clicked
|
2025-11-16 19:32:07 +01:00
|
|
|
# The event handler checks: elif text == lv.SYMBOL.DOWN or text == self.LABEL_LETTERS:
|
|
|
|
|
# Then it calls: self._keyboard.set_map() and self._keyboard.set_mode()
|
2025-11-17 12:27:08 +01:00
|
|
|
print("\nStep 2: Simulate clicking 'Abc' (via event handler logic)")
|
2025-11-16 19:32:07 +01:00
|
|
|
|
|
|
|
|
# This is what the event handler does:
|
|
|
|
|
keyboard._keyboard.set_map(
|
|
|
|
|
MposKeyboard.MODE_LOWERCASE,
|
|
|
|
|
keyboard._lowercase_map,
|
|
|
|
|
keyboard._lowercase_ctrl
|
|
|
|
|
)
|
|
|
|
|
keyboard._keyboard.set_mode(MposKeyboard.MODE_LOWERCASE)
|
|
|
|
|
wait_for_render(5)
|
|
|
|
|
|
|
|
|
|
# Verify we're back in lowercase mode with OUR custom layout
|
|
|
|
|
# When in LOWERCASE mode:
|
|
|
|
|
# - Our custom keyboard has "?123" (to switch to numbers)
|
|
|
|
|
# - Default LVGL keyboard has "1#" (to switch to numbers) and "ABC" (to switch to uppercase)
|
|
|
|
|
print("\nStep 3: Verify we have custom lowercase layout (not default LVGL)")
|
|
|
|
|
|
|
|
|
|
found_labels = {}
|
|
|
|
|
for i in range(100):
|
|
|
|
|
try:
|
|
|
|
|
text = keyboard.get_button_text(i)
|
2025-11-17 12:27:08 +01:00
|
|
|
if text in ["Abc", "ABC", "?123", "1#", lv.SYMBOL.UP]:
|
2025-11-16 19:32:07 +01:00
|
|
|
found_labels[text] = i
|
|
|
|
|
print(f" Found label '{text}' at index {i}")
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
# Check for WRONG labels (default LVGL keyboard)
|
|
|
|
|
if "ABC" in found_labels:
|
|
|
|
|
print(f" ERROR: Found 'ABC' - this is the DEFAULT LVGL keyboard!")
|
|
|
|
|
print(" Found these labels:", list(found_labels.keys()))
|
|
|
|
|
self.fail("BUG DETECTED: Event handler caused switch to default LVGL keyboard with 'ABC' label")
|
|
|
|
|
|
|
|
|
|
if "1#" in found_labels:
|
|
|
|
|
print(f" ERROR: Found '1#' - this is the DEFAULT LVGL keyboard!")
|
|
|
|
|
print(" Found these labels:", list(found_labels.keys()))
|
|
|
|
|
self.fail("BUG DETECTED: Event handler caused switch to default LVGL keyboard with '1#' label")
|
|
|
|
|
|
|
|
|
|
# Check for CORRECT labels (our custom keyboard in lowercase mode)
|
|
|
|
|
self.assertIn("?123", found_labels,
|
|
|
|
|
"Should find '?123' label in custom lowercase mode (not '1#' from default)")
|
|
|
|
|
self.assertIn(lv.SYMBOL.UP, found_labels,
|
|
|
|
|
"Should find UP symbol in custom lowercase mode")
|
|
|
|
|
|
|
|
|
|
print(f" Found '?123' at index {found_labels['?123']} - GOOD")
|
|
|
|
|
print(f" Found UP symbol at index {found_labels[lv.SYMBOL.UP]} - GOOD")
|
|
|
|
|
print("\nSUCCESS: Event handler preserves custom layout!")
|
|
|
|
|
|
|
|
|
|
|