forked from MicroPythonOS/MicroPythonOS
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_graphical_keyboard_styling.py
More file actions
376 lines (296 loc) · 12.9 KB
/
test_graphical_keyboard_styling.py
File metadata and controls
376 lines (296 loc) · 12.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
"""
Graphical test for on-screen keyboard button styling.
This test verifies that keyboard buttons have proper visible contrast
in both light and dark modes. It checks for the bug where keyboard buttons
appear white-on-white in light mode on ESP32.
The test uses two approaches:
1. Programmatic: Query LVGL style properties to verify button background colors
2. Visual: Capture screenshots for manual verification and regression testing
This test should INITIALLY FAIL, demonstrating the bug before the fix is applied.
Usage:
Desktop: ./tests/unittest.sh tests/test_graphical_keyboard_styling.py
Device: ./tests/unittest.sh tests/test_graphical_keyboard_styling.py --ondevice
"""
import unittest
import lvgl as lv
import mpos.ui
import mpos.config
import sys
import os
from mpos import (
wait_for_render,
capture_screenshot,
AppearanceManager,
)
class TestKeyboardStyling(unittest.TestCase):
"""Test suite for keyboard button visibility and styling."""
def setUp(self):
"""Set up test fixtures before each test method."""
# Determine screenshot directory
if sys.platform == "esp32":
self.screenshot_dir = "tests/screenshots"
else:
self.screenshot_dir = "../tests/screenshots"
# Ensure screenshots directory exists
try:
os.mkdir(self.screenshot_dir)
except OSError:
pass # Directory already exists
# Save current theme setting
prefs = mpos.config.SharedPreferences("theme_settings")
self.original_theme = prefs.get_string("theme_light_dark", "light")
print(f"\n=== Keyboard Styling Test Setup ===")
print(f"Platform: {sys.platform}")
print(f"Original theme: {self.original_theme}")
def tearDown(self):
"""Clean up after each test method."""
# Restore original theme
prefs = mpos.config.SharedPreferences("theme_settings")
editor = prefs.edit()
editor.put_string("theme_light_dark", self.original_theme)
editor.commit()
# Reapply original theme
AppearanceManager.set_theme(prefs)
print("=== Test cleanup complete ===\n")
def _create_test_keyboard(self):
"""
Create a test keyboard widget for inspection.
Returns:
tuple: (screen, keyboard, textarea) widgets
"""
# Create a clean screen
screen = lv.obj()
screen.set_size(320, 240)
# Create a text area for the keyboard to target
textarea = lv.textarea(screen)
textarea.set_size(280, 40)
textarea.align(lv.ALIGN.TOP_MID, 0, 10)
textarea.set_placeholder_text("Type here...")
# Create the keyboard
keyboard = lv.keyboard(screen)
keyboard.set_textarea(textarea)
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
keyboard.set_style_min_height(160, 0)
# Apply the keyboard button fix
AppearanceManager.apply_keyboard_fix(keyboard)
# Load the screen and wait for rendering
lv.screen_load(screen)
wait_for_render(iterations=20)
return screen, keyboard, textarea
def _get_button_background_color(self, keyboard):
"""
Extract the background color of keyboard buttons.
This queries LVGL's style system to get the actual rendered
background color of the keyboard's button parts (LV_PART_ITEMS).
Args:
keyboard: LVGL keyboard widget
Returns:
dict: Color information with 'r', 'g', 'b' values (0-255)
"""
# Get the style property for button background color
# LV_PART_ITEMS is the part that represents individual buttons
bg_color = keyboard.get_style_bg_color(lv.PART.ITEMS)
# Extract RGB values from LVGL color
# Note: LVGL colors are in RGB565 or RGB888 depending on config
# We convert to RGB888 for comparison
r = lv.color_brightness(bg_color) if hasattr(lv, 'color_brightness') else 0
# Try to get RGB components directly
try:
# For LVGL 9.x, colors have direct accessors
color_dict = {
'r': bg_color.red() if hasattr(bg_color, 'red') else 0,
'g': bg_color.green() if hasattr(bg_color, 'green') else 0,
'b': bg_color.blue() if hasattr(bg_color, 'blue') else 0,
}
except:
# Fallback: use color as hex value
try:
color_int = bg_color.to_int() if hasattr(bg_color, 'to_int') else 0
color_dict = {
'r': (color_int >> 16) & 0xFF,
'g': (color_int >> 8) & 0xFF,
'b': color_int & 0xFF,
'hex': f"#{color_int:06x}"
}
except:
# Last resort: just store the color object
color_dict = {'color_obj': bg_color}
return color_dict
def _get_screen_background_color(self, screen):
"""
Extract the background color of the screen.
Args:
screen: LVGL screen object
Returns:
dict: Color information with 'r', 'g', 'b' values (0-255)
"""
bg_color = screen.get_style_bg_color(lv.PART.MAIN)
try:
color_dict = {
'r': bg_color.red() if hasattr(bg_color, 'red') else 0,
'g': bg_color.green() if hasattr(bg_color, 'green') else 0,
'b': bg_color.blue() if hasattr(bg_color, 'blue') else 0,
}
except:
try:
color_int = bg_color.to_int() if hasattr(bg_color, 'to_int') else 0
color_dict = {
'r': (color_int >> 16) & 0xFF,
'g': (color_int >> 8) & 0xFF,
'b': color_int & 0xFF,
'hex': f"#{color_int:06x}"
}
except:
color_dict = {'color_obj': bg_color}
return color_dict
def _color_contrast_sufficient(self, color1, color2, min_difference=20):
"""
Check if two colors have sufficient contrast.
Uses simple RGB distance. For production, you might want to use
proper contrast ratio calculation (WCAG).
Args:
color1: Dict with 'r', 'g', 'b' keys
color2: Dict with 'r', 'g', 'b' keys
min_difference: Minimum RGB distance for sufficient contrast
Returns:
bool: True if contrast is sufficient
"""
if 'r' not in color1 or 'r' not in color2:
# Can't determine, assume failure
return False
# Calculate Euclidean distance in RGB space
r_diff = abs(color1['r'] - color2['r'])
g_diff = abs(color1['g'] - color2['g'])
b_diff = abs(color1['b'] - color2['b'])
# Simple average difference
avg_diff = (r_diff + g_diff + b_diff) / 3
print(f" Color 1: RGB({color1['r']}, {color1['g']}, {color1['b']})")
print(f" Color 2: RGB({color2['r']}, {color2['g']}, {color2['b']})")
print(f" Average difference: {avg_diff:.1f} (min required: {min_difference})")
return avg_diff >= min_difference
def test_keyboard_buttons_visible_in_light_mode(self):
"""
Test that keyboard buttons are visible in light mode.
In light mode, the screen background is white. Keyboard buttons
should NOT be white - they should be a light gray color to provide
contrast.
This test will FAIL initially, demonstrating the bug.
"""
print("\n=== Testing keyboard buttons in LIGHT mode ===")
# Set theme to light mode
prefs = mpos.config.SharedPreferences("theme_settings")
editor = prefs.edit()
editor.put_string("theme_light_dark", "light")
editor.commit()
# Apply theme
AppearanceManager.set_theme(prefs)
wait_for_render(iterations=10)
# Create test keyboard
screen, keyboard, textarea = self._create_test_keyboard()
# Get colors
button_bg = self._get_button_background_color(keyboard)
screen_bg = self._get_screen_background_color(screen)
print("\nLight mode colors:")
print(f" Screen background: {screen_bg}")
print(f" Button background: {button_bg}")
# Capture screenshot
screenshot_path = f"{self.screenshot_dir}/keyboard_light_mode.raw"
print(f"\nCapturing screenshot: {screenshot_path}")
capture_screenshot(screenshot_path, width=320, height=240)
# Verify contrast
print("\nChecking button/screen contrast...")
has_contrast = self._color_contrast_sufficient(button_bg, screen_bg, min_difference=20)
# Clean up
lv.screen_load(lv.obj())
wait_for_render(5)
# Assert: buttons should have sufficient contrast with background
self.assertTrue(
has_contrast,
f"Keyboard buttons lack sufficient contrast in light mode!\n"
f"Button color: {button_bg}\n"
f"Screen color: {screen_bg}\n"
f"This is the BUG we're trying to fix - buttons are white on white."
)
print("=== Light mode test PASSED ===")
def test_keyboard_buttons_visible_in_dark_mode(self):
"""
Test that keyboard buttons are visible in dark mode.
In dark mode, buttons should have proper contrast with the
dark background. This typically works correctly.
"""
print("\n=== Testing keyboard buttons in DARK mode ===")
# Set theme to dark mode
prefs = mpos.config.SharedPreferences("theme_settings")
editor = prefs.edit()
editor.put_string("theme_light_dark", "dark")
editor.commit()
# Apply theme
AppearanceManager.set_theme(prefs)
wait_for_render(iterations=10)
# Create test keyboard
screen, keyboard, textarea = self._create_test_keyboard()
# Get colors
button_bg = self._get_button_background_color(keyboard)
screen_bg = self._get_screen_background_color(screen)
print("\nDark mode colors:")
print(f" Screen background: {screen_bg}")
print(f" Button background: {button_bg}")
# Capture screenshot
screenshot_path = f"{self.screenshot_dir}/keyboard_dark_mode.raw"
print(f"\nCapturing screenshot: {screenshot_path}")
capture_screenshot(screenshot_path, width=320, height=240)
# Verify contrast
print("\nChecking button/screen contrast...")
has_contrast = self._color_contrast_sufficient(button_bg, screen_bg, min_difference=20)
# Clean up
lv.screen_load(lv.obj())
wait_for_render(5)
# Assert: buttons should have sufficient contrast
self.assertTrue(
has_contrast,
f"Keyboard buttons lack sufficient contrast in dark mode!\n"
f"Button color: {button_bg}\n"
f"Screen color: {screen_bg}"
)
print("=== Dark mode test PASSED ===")
def test_keyboard_buttons_not_pure_white_in_light_mode(self):
"""
Specific test: In light mode, buttons should NOT be pure white.
They should be a light gray (approximately RGB(238, 238, 238) or similar).
Pure white (255, 255, 255) means they're invisible on white background.
"""
print("\n=== Testing that buttons are NOT pure white in light mode ===")
# Set theme to light mode
prefs = mpos.config.SharedPreferences("theme_settings")
editor = prefs.edit()
editor.put_string("theme_light_dark", "light")
editor.commit()
# Apply theme
AppearanceManager.set_theme(prefs)
wait_for_render(iterations=10)
# Create test keyboard
screen, keyboard, textarea = self._create_test_keyboard()
# Get button color
button_bg = self._get_button_background_color(keyboard)
print(f"\nButton background color: {button_bg}")
# Clean up
lv.screen_load(lv.obj())
wait_for_render(5)
# Check if button is pure white (or very close to it)
if 'r' in button_bg:
is_white = (button_bg['r'] >= 250 and
button_bg['g'] >= 250 and
button_bg['b'] >= 250)
print(f"Is button pure white? {is_white}")
# Assert: buttons should NOT be pure white
self.assertFalse(
is_white,
f"Keyboard buttons are pure white in light mode!\n"
f"Button color: RGB({button_bg['r']}, {button_bg['g']}, {button_bg['b']})\n"
f"Expected: Light gray around RGB(238, 238, 238) or similar\n"
f"This is the BUG - white buttons on white background are invisible."
)
else:
# Couldn't extract RGB, fail the test
self.fail(f"Could not extract RGB values from button color: {button_bg}")
print("=== Pure white test PASSED ===")