You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
Add q button test
This commit is contained in:
@@ -275,6 +275,83 @@ def print_screen_labels(obj):
|
||||
print(f" {i}: {text}")
|
||||
|
||||
|
||||
def get_widget_coords(widget):
|
||||
"""
|
||||
Get the coordinates of a widget.
|
||||
|
||||
Returns the bounding box coordinates of the widget, useful for
|
||||
clicking on it or verifying its position.
|
||||
|
||||
Args:
|
||||
widget: LVGL widget object
|
||||
|
||||
Returns:
|
||||
dict: Dictionary with keys 'x1', 'y1', 'x2', 'y2', 'center_x', 'center_y'
|
||||
Returns None if widget is invalid or has no coordinates
|
||||
|
||||
Example:
|
||||
# Find and click on a button
|
||||
button = find_label_with_text(lv.screen_active(), "Submit")
|
||||
if button:
|
||||
coords = get_widget_coords(button.get_parent()) # Get parent button
|
||||
if coords:
|
||||
simulate_click(coords['center_x'], coords['center_y'])
|
||||
"""
|
||||
try:
|
||||
area = lv.area_t()
|
||||
widget.get_coords(area)
|
||||
return {
|
||||
'x1': area.x1,
|
||||
'y1': area.y1,
|
||||
'x2': area.x2,
|
||||
'y2': area.y2,
|
||||
'center_x': (area.x1 + area.x2) // 2,
|
||||
'center_y': (area.y1 + area.y2) // 2,
|
||||
'width': area.x2 - area.x1,
|
||||
'height': area.y2 - area.y1,
|
||||
}
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def find_button_with_text(obj, search_text):
|
||||
"""
|
||||
Find a button widget containing specific text in its label.
|
||||
|
||||
This is specifically for finding buttons (which contain labels as children)
|
||||
rather than just labels. Very useful for testing UI interactions.
|
||||
|
||||
Args:
|
||||
obj: LVGL object to search (typically lv.screen_active())
|
||||
search_text: Text to search for in button labels (can be substring)
|
||||
|
||||
Returns:
|
||||
LVGL button object if found, None otherwise
|
||||
|
||||
Example:
|
||||
submit_btn = find_button_with_text(lv.screen_active(), "Submit")
|
||||
if submit_btn:
|
||||
coords = get_widget_coords(submit_btn)
|
||||
simulate_click(coords['center_x'], coords['center_y'])
|
||||
"""
|
||||
# Find the label first
|
||||
label = find_label_with_text(obj, search_text)
|
||||
if label:
|
||||
# Try to get the parent button
|
||||
try:
|
||||
parent = label.get_parent()
|
||||
# Check if parent is a button
|
||||
if parent.get_class() == lv.button_class:
|
||||
return parent
|
||||
# Sometimes there's an extra container layer
|
||||
grandparent = parent.get_parent()
|
||||
if grandparent and grandparent.get_class() == lv.button_class:
|
||||
return grandparent
|
||||
except:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _touch_read_cb(indev_drv, data):
|
||||
"""
|
||||
Internal callback for simulated touch input device.
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
"""
|
||||
Test for keyboard "q" button bug.
|
||||
|
||||
This test reproduces the issue where typing "q" on the keyboard results in
|
||||
the button lighting up but no character being added to the textarea, while
|
||||
the "a" button beneath it works correctly.
|
||||
|
||||
The test uses helper functions to locate buttons by their text, get their
|
||||
coordinates, and simulate clicks using simulate_click().
|
||||
|
||||
Usage:
|
||||
Desktop: ./tests/unittest.sh tests/test_graphical_keyboard_q_button_bug.py
|
||||
Device: ./tests/unittest.sh tests/test_graphical_keyboard_q_button_bug.py --ondevice
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import lvgl as lv
|
||||
from mpos.ui.keyboard import MposKeyboard
|
||||
from mpos.ui.testing import (
|
||||
wait_for_render,
|
||||
find_button_with_text,
|
||||
get_widget_coords,
|
||||
simulate_click,
|
||||
print_screen_labels
|
||||
)
|
||||
|
||||
|
||||
class TestKeyboardQButtonBug(unittest.TestCase):
|
||||
"""Test keyboard 'q' button behavior vs 'a' button."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
self.screen = lv.obj()
|
||||
self.screen.set_size(320, 240)
|
||||
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_q_button_bug(self):
|
||||
"""
|
||||
Test that clicking the 'q' button adds 'q' to textarea.
|
||||
|
||||
This test demonstrates the bug where:
|
||||
1. Clicking 'q' button lights it up but doesn't add to textarea
|
||||
2. Clicking 'a' button works correctly
|
||||
|
||||
Steps:
|
||||
1. Create textarea and keyboard
|
||||
2. Find 'q' button index in keyboard map
|
||||
3. Get button coordinates from keyboard widget
|
||||
4. Click it using simulate_click()
|
||||
5. Verify 'q' appears in textarea (EXPECTED TO FAIL due to bug)
|
||||
6. Repeat with 'a' button
|
||||
7. Verify 'a' appears correctly (EXPECTED TO PASS)
|
||||
"""
|
||||
print("\n=== Testing keyboard 'q' and 'a' button behavior ===")
|
||||
|
||||
# Create textarea
|
||||
textarea = lv.textarea(self.screen)
|
||||
textarea.set_size(200, 30)
|
||||
textarea.set_one_line(True)
|
||||
textarea.align(lv.ALIGN.TOP_MID, 0, 10)
|
||||
textarea.set_text("") # Start empty
|
||||
wait_for_render(5)
|
||||
|
||||
# Create keyboard and connect to textarea
|
||||
keyboard = MposKeyboard(self.screen)
|
||||
keyboard.set_textarea(textarea)
|
||||
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
||||
wait_for_render(10)
|
||||
|
||||
print(f"Initial textarea: '{textarea.get_text()}'")
|
||||
self.assertEqual(textarea.get_text(), "", "Textarea should start empty")
|
||||
|
||||
# --- Test 'q' button ---
|
||||
print("\n--- Testing 'q' button ---")
|
||||
|
||||
# Find button index for 'q' in the keyboard
|
||||
q_button_id = None
|
||||
for i in range(100): # Check first 100 button indices
|
||||
try:
|
||||
text = keyboard.get_button_text(i)
|
||||
if text == "q":
|
||||
q_button_id = i
|
||||
print(f"Found 'q' button at index {i}")
|
||||
break
|
||||
except:
|
||||
break # No more buttons
|
||||
|
||||
self.assertIsNotNone(q_button_id, "Should find 'q' button on keyboard")
|
||||
|
||||
# Get the keyboard widget coordinates to calculate button position
|
||||
keyboard_area = lv.area_t()
|
||||
keyboard.get_coords(keyboard_area)
|
||||
print(f"Keyboard area: x1={keyboard_area.x1}, y1={keyboard_area.y1}, x2={keyboard_area.x2}, y2={keyboard_area.y2}")
|
||||
|
||||
# LVGL keyboards organize buttons in a grid
|
||||
# From the map: "q" is at index 0, in top row (10 buttons per row)
|
||||
# Let's estimate position based on keyboard layout
|
||||
# Top row starts at y1 + some padding, each button is ~width/10
|
||||
keyboard_width = keyboard_area.x2 - keyboard_area.x1
|
||||
keyboard_height = keyboard_area.y2 - keyboard_area.y1
|
||||
button_width = keyboard_width // 10 # ~10 buttons per row
|
||||
button_height = keyboard_height // 4 # ~4 rows
|
||||
|
||||
# 'q' is first button (index 0), top row
|
||||
q_x = keyboard_area.x1 + button_width // 2
|
||||
q_y = keyboard_area.y1 + button_height // 2
|
||||
|
||||
print(f"Estimated 'q' button position: ({q_x}, {q_y})")
|
||||
|
||||
# Click the 'q' button
|
||||
print(f"Clicking 'q' button at ({q_x}, {q_y})")
|
||||
simulate_click(q_x, q_y)
|
||||
wait_for_render(10)
|
||||
|
||||
# Check textarea content
|
||||
text_after_q = textarea.get_text()
|
||||
print(f"Textarea after clicking 'q': '{text_after_q}'")
|
||||
|
||||
# THIS IS THE BUG: 'q' should be added but isn't
|
||||
if text_after_q != "q":
|
||||
print("BUG REPRODUCED: 'q' button was clicked but 'q' was NOT added to textarea!")
|
||||
print("Expected: 'q'")
|
||||
print(f"Got: '{text_after_q}'")
|
||||
|
||||
self.assertEqual(text_after_q, "q",
|
||||
"Clicking 'q' button should add 'q' to textarea (BUG: This test will fail)")
|
||||
|
||||
# --- Test 'a' button for comparison ---
|
||||
print("\n--- Testing 'a' button (for comparison) ---")
|
||||
|
||||
# Clear textarea
|
||||
textarea.set_text("")
|
||||
wait_for_render(5)
|
||||
print("Cleared textarea")
|
||||
|
||||
# Find button index for 'a'
|
||||
a_button_id = None
|
||||
for i in range(100):
|
||||
try:
|
||||
text = keyboard.get_button_text(i)
|
||||
if text == "a":
|
||||
a_button_id = i
|
||||
print(f"Found 'a' button at index {i}")
|
||||
break
|
||||
except:
|
||||
break
|
||||
|
||||
self.assertIsNotNone(a_button_id, "Should find 'a' button on keyboard")
|
||||
|
||||
# 'a' is at index 11 (second row, first position)
|
||||
a_x = keyboard_area.x1 + button_width // 2
|
||||
a_y = keyboard_area.y1 + button_height + button_height // 2
|
||||
|
||||
print(f"Estimated 'a' button position: ({a_x}, {a_y})")
|
||||
|
||||
# Click the 'a' button
|
||||
print(f"Clicking 'a' button at ({a_x}, {a_y})")
|
||||
simulate_click(a_x, a_y)
|
||||
wait_for_render(10)
|
||||
|
||||
# Check textarea content
|
||||
text_after_a = textarea.get_text()
|
||||
print(f"Textarea after clicking 'a': '{text_after_a}'")
|
||||
|
||||
# The 'a' button should work correctly
|
||||
self.assertEqual(text_after_a, "a",
|
||||
"Clicking 'a' button should add 'a' to textarea (should PASS)")
|
||||
|
||||
print("\nSummary:")
|
||||
print(f" 'q' button result: '{text_after_q}' (expected 'q')")
|
||||
print(f" 'a' button result: '{text_after_a}' (expected 'a')")
|
||||
if text_after_q != "q" and text_after_a == "a":
|
||||
print(" BUG CONFIRMED: 'q' doesn't work but 'a' does!")
|
||||
|
||||
def test_keyboard_button_discovery(self):
|
||||
"""
|
||||
Debug test: Discover all buttons on the keyboard.
|
||||
|
||||
This test helps understand the keyboard layout and button structure.
|
||||
It prints all found buttons and their text.
|
||||
"""
|
||||
print("\n=== Discovering keyboard buttons ===")
|
||||
|
||||
# Create keyboard without textarea to inspect it
|
||||
keyboard = MposKeyboard(self.screen)
|
||||
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
||||
wait_for_render(10)
|
||||
|
||||
# Iterate through button indices to find all buttons
|
||||
print("\nEnumerating keyboard buttons by index:")
|
||||
found_buttons = []
|
||||
|
||||
for i in range(100): # Check first 100 indices
|
||||
try:
|
||||
text = keyboard.get_button_text(i)
|
||||
if text: # Skip None/empty
|
||||
found_buttons.append((i, text))
|
||||
# Only print first 20 to avoid clutter
|
||||
if i < 20:
|
||||
print(f" Button {i}: '{text}'")
|
||||
except:
|
||||
# No more buttons
|
||||
break
|
||||
|
||||
if len(found_buttons) > 20:
|
||||
print(f" ... (showing first 20 of {len(found_buttons)} buttons)")
|
||||
|
||||
print(f"\nTotal buttons found: {len(found_buttons)}")
|
||||
|
||||
# Try to find specific letters
|
||||
letters_to_test = ['q', 'w', 'e', 'r', 'a', 's', 'd', 'f']
|
||||
print("\nLooking for specific letters:")
|
||||
|
||||
for letter in letters_to_test:
|
||||
found = False
|
||||
for idx, text in found_buttons:
|
||||
if text == letter:
|
||||
print(f" '{letter}' at index {idx}")
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
print(f" '{letter}' NOT FOUND")
|
||||
|
||||
# Verify we can find at least some buttons
|
||||
self.assertTrue(len(found_buttons) > 0,
|
||||
"Should find at least some buttons on keyboard")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user