Skip to content

Commit 7e9e235

Browse files
Add q button test
1 parent 10c869a commit 7e9e235

File tree

2 files changed

+313
-0
lines changed

2 files changed

+313
-0
lines changed

internal_filesystem/lib/mpos/ui/testing.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,83 @@ def print_screen_labels(obj):
275275
print(f" {i}: {text}")
276276

277277

278+
def get_widget_coords(widget):
279+
"""
280+
Get the coordinates of a widget.
281+
282+
Returns the bounding box coordinates of the widget, useful for
283+
clicking on it or verifying its position.
284+
285+
Args:
286+
widget: LVGL widget object
287+
288+
Returns:
289+
dict: Dictionary with keys 'x1', 'y1', 'x2', 'y2', 'center_x', 'center_y'
290+
Returns None if widget is invalid or has no coordinates
291+
292+
Example:
293+
# Find and click on a button
294+
button = find_label_with_text(lv.screen_active(), "Submit")
295+
if button:
296+
coords = get_widget_coords(button.get_parent()) # Get parent button
297+
if coords:
298+
simulate_click(coords['center_x'], coords['center_y'])
299+
"""
300+
try:
301+
area = lv.area_t()
302+
widget.get_coords(area)
303+
return {
304+
'x1': area.x1,
305+
'y1': area.y1,
306+
'x2': area.x2,
307+
'y2': area.y2,
308+
'center_x': (area.x1 + area.x2) // 2,
309+
'center_y': (area.y1 + area.y2) // 2,
310+
'width': area.x2 - area.x1,
311+
'height': area.y2 - area.y1,
312+
}
313+
except:
314+
return None
315+
316+
317+
def find_button_with_text(obj, search_text):
318+
"""
319+
Find a button widget containing specific text in its label.
320+
321+
This is specifically for finding buttons (which contain labels as children)
322+
rather than just labels. Very useful for testing UI interactions.
323+
324+
Args:
325+
obj: LVGL object to search (typically lv.screen_active())
326+
search_text: Text to search for in button labels (can be substring)
327+
328+
Returns:
329+
LVGL button object if found, None otherwise
330+
331+
Example:
332+
submit_btn = find_button_with_text(lv.screen_active(), "Submit")
333+
if submit_btn:
334+
coords = get_widget_coords(submit_btn)
335+
simulate_click(coords['center_x'], coords['center_y'])
336+
"""
337+
# Find the label first
338+
label = find_label_with_text(obj, search_text)
339+
if label:
340+
# Try to get the parent button
341+
try:
342+
parent = label.get_parent()
343+
# Check if parent is a button
344+
if parent.get_class() == lv.button_class:
345+
return parent
346+
# Sometimes there's an extra container layer
347+
grandparent = parent.get_parent()
348+
if grandparent and grandparent.get_class() == lv.button_class:
349+
return grandparent
350+
except:
351+
pass
352+
return None
353+
354+
278355
def _touch_read_cb(indev_drv, data):
279356
"""
280357
Internal callback for simulated touch input device.
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
"""
2+
Test for keyboard "q" button bug.
3+
4+
This test reproduces the issue where typing "q" on the keyboard results in
5+
the button lighting up but no character being added to the textarea, while
6+
the "a" button beneath it works correctly.
7+
8+
The test uses helper functions to locate buttons by their text, get their
9+
coordinates, and simulate clicks using simulate_click().
10+
11+
Usage:
12+
Desktop: ./tests/unittest.sh tests/test_graphical_keyboard_q_button_bug.py
13+
Device: ./tests/unittest.sh tests/test_graphical_keyboard_q_button_bug.py --ondevice
14+
"""
15+
16+
import unittest
17+
import lvgl as lv
18+
from mpos.ui.keyboard import MposKeyboard
19+
from mpos.ui.testing import (
20+
wait_for_render,
21+
find_button_with_text,
22+
get_widget_coords,
23+
simulate_click,
24+
print_screen_labels
25+
)
26+
27+
28+
class TestKeyboardQButtonBug(unittest.TestCase):
29+
"""Test keyboard 'q' button behavior vs 'a' button."""
30+
31+
def setUp(self):
32+
"""Set up test fixtures."""
33+
self.screen = lv.obj()
34+
self.screen.set_size(320, 240)
35+
lv.screen_load(self.screen)
36+
wait_for_render(5)
37+
38+
def tearDown(self):
39+
"""Clean up."""
40+
lv.screen_load(lv.obj())
41+
wait_for_render(5)
42+
43+
def test_q_button_bug(self):
44+
"""
45+
Test that clicking the 'q' button adds 'q' to textarea.
46+
47+
This test demonstrates the bug where:
48+
1. Clicking 'q' button lights it up but doesn't add to textarea
49+
2. Clicking 'a' button works correctly
50+
51+
Steps:
52+
1. Create textarea and keyboard
53+
2. Find 'q' button index in keyboard map
54+
3. Get button coordinates from keyboard widget
55+
4. Click it using simulate_click()
56+
5. Verify 'q' appears in textarea (EXPECTED TO FAIL due to bug)
57+
6. Repeat with 'a' button
58+
7. Verify 'a' appears correctly (EXPECTED TO PASS)
59+
"""
60+
print("\n=== Testing keyboard 'q' and 'a' button behavior ===")
61+
62+
# Create textarea
63+
textarea = lv.textarea(self.screen)
64+
textarea.set_size(200, 30)
65+
textarea.set_one_line(True)
66+
textarea.align(lv.ALIGN.TOP_MID, 0, 10)
67+
textarea.set_text("") # Start empty
68+
wait_for_render(5)
69+
70+
# Create keyboard and connect to textarea
71+
keyboard = MposKeyboard(self.screen)
72+
keyboard.set_textarea(textarea)
73+
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
74+
wait_for_render(10)
75+
76+
print(f"Initial textarea: '{textarea.get_text()}'")
77+
self.assertEqual(textarea.get_text(), "", "Textarea should start empty")
78+
79+
# --- Test 'q' button ---
80+
print("\n--- Testing 'q' button ---")
81+
82+
# Find button index for 'q' in the keyboard
83+
q_button_id = None
84+
for i in range(100): # Check first 100 button indices
85+
try:
86+
text = keyboard.get_button_text(i)
87+
if text == "q":
88+
q_button_id = i
89+
print(f"Found 'q' button at index {i}")
90+
break
91+
except:
92+
break # No more buttons
93+
94+
self.assertIsNotNone(q_button_id, "Should find 'q' button on keyboard")
95+
96+
# Get the keyboard widget coordinates to calculate button position
97+
keyboard_area = lv.area_t()
98+
keyboard.get_coords(keyboard_area)
99+
print(f"Keyboard area: x1={keyboard_area.x1}, y1={keyboard_area.y1}, x2={keyboard_area.x2}, y2={keyboard_area.y2}")
100+
101+
# LVGL keyboards organize buttons in a grid
102+
# From the map: "q" is at index 0, in top row (10 buttons per row)
103+
# Let's estimate position based on keyboard layout
104+
# Top row starts at y1 + some padding, each button is ~width/10
105+
keyboard_width = keyboard_area.x2 - keyboard_area.x1
106+
keyboard_height = keyboard_area.y2 - keyboard_area.y1
107+
button_width = keyboard_width // 10 # ~10 buttons per row
108+
button_height = keyboard_height // 4 # ~4 rows
109+
110+
# 'q' is first button (index 0), top row
111+
q_x = keyboard_area.x1 + button_width // 2
112+
q_y = keyboard_area.y1 + button_height // 2
113+
114+
print(f"Estimated 'q' button position: ({q_x}, {q_y})")
115+
116+
# Click the 'q' button
117+
print(f"Clicking 'q' button at ({q_x}, {q_y})")
118+
simulate_click(q_x, q_y)
119+
wait_for_render(10)
120+
121+
# Check textarea content
122+
text_after_q = textarea.get_text()
123+
print(f"Textarea after clicking 'q': '{text_after_q}'")
124+
125+
# THIS IS THE BUG: 'q' should be added but isn't
126+
if text_after_q != "q":
127+
print("BUG REPRODUCED: 'q' button was clicked but 'q' was NOT added to textarea!")
128+
print("Expected: 'q'")
129+
print(f"Got: '{text_after_q}'")
130+
131+
self.assertEqual(text_after_q, "q",
132+
"Clicking 'q' button should add 'q' to textarea (BUG: This test will fail)")
133+
134+
# --- Test 'a' button for comparison ---
135+
print("\n--- Testing 'a' button (for comparison) ---")
136+
137+
# Clear textarea
138+
textarea.set_text("")
139+
wait_for_render(5)
140+
print("Cleared textarea")
141+
142+
# Find button index for 'a'
143+
a_button_id = None
144+
for i in range(100):
145+
try:
146+
text = keyboard.get_button_text(i)
147+
if text == "a":
148+
a_button_id = i
149+
print(f"Found 'a' button at index {i}")
150+
break
151+
except:
152+
break
153+
154+
self.assertIsNotNone(a_button_id, "Should find 'a' button on keyboard")
155+
156+
# 'a' is at index 11 (second row, first position)
157+
a_x = keyboard_area.x1 + button_width // 2
158+
a_y = keyboard_area.y1 + button_height + button_height // 2
159+
160+
print(f"Estimated 'a' button position: ({a_x}, {a_y})")
161+
162+
# Click the 'a' button
163+
print(f"Clicking 'a' button at ({a_x}, {a_y})")
164+
simulate_click(a_x, a_y)
165+
wait_for_render(10)
166+
167+
# Check textarea content
168+
text_after_a = textarea.get_text()
169+
print(f"Textarea after clicking 'a': '{text_after_a}'")
170+
171+
# The 'a' button should work correctly
172+
self.assertEqual(text_after_a, "a",
173+
"Clicking 'a' button should add 'a' to textarea (should PASS)")
174+
175+
print("\nSummary:")
176+
print(f" 'q' button result: '{text_after_q}' (expected 'q')")
177+
print(f" 'a' button result: '{text_after_a}' (expected 'a')")
178+
if text_after_q != "q" and text_after_a == "a":
179+
print(" BUG CONFIRMED: 'q' doesn't work but 'a' does!")
180+
181+
def test_keyboard_button_discovery(self):
182+
"""
183+
Debug test: Discover all buttons on the keyboard.
184+
185+
This test helps understand the keyboard layout and button structure.
186+
It prints all found buttons and their text.
187+
"""
188+
print("\n=== Discovering keyboard buttons ===")
189+
190+
# Create keyboard without textarea to inspect it
191+
keyboard = MposKeyboard(self.screen)
192+
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
193+
wait_for_render(10)
194+
195+
# Iterate through button indices to find all buttons
196+
print("\nEnumerating keyboard buttons by index:")
197+
found_buttons = []
198+
199+
for i in range(100): # Check first 100 indices
200+
try:
201+
text = keyboard.get_button_text(i)
202+
if text: # Skip None/empty
203+
found_buttons.append((i, text))
204+
# Only print first 20 to avoid clutter
205+
if i < 20:
206+
print(f" Button {i}: '{text}'")
207+
except:
208+
# No more buttons
209+
break
210+
211+
if len(found_buttons) > 20:
212+
print(f" ... (showing first 20 of {len(found_buttons)} buttons)")
213+
214+
print(f"\nTotal buttons found: {len(found_buttons)}")
215+
216+
# Try to find specific letters
217+
letters_to_test = ['q', 'w', 'e', 'r', 'a', 's', 'd', 'f']
218+
print("\nLooking for specific letters:")
219+
220+
for letter in letters_to_test:
221+
found = False
222+
for idx, text in found_buttons:
223+
if text == letter:
224+
print(f" '{letter}' at index {idx}")
225+
found = True
226+
break
227+
if not found:
228+
print(f" '{letter}' NOT FOUND")
229+
230+
# Verify we can find at least some buttons
231+
self.assertTrue(len(found_buttons) > 0,
232+
"Should find at least some buttons on keyboard")
233+
234+
235+
if __name__ == "__main__":
236+
unittest.main()

0 commit comments

Comments
 (0)