You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
Remove tests/base/* stuff to fix --ondevice tests
This commit is contained in:
@@ -708,20 +708,7 @@ class UpdateChecker:
|
||||
self.json = json_module if json_module else ujson
|
||||
|
||||
def get_update_url(self, hardware_id):
|
||||
"""Determine the update JSON URL based on hardware ID.
|
||||
|
||||
Args:
|
||||
hardware_id: Hardware identifier string
|
||||
|
||||
Returns:
|
||||
str: Full URL to update JSON file
|
||||
"""
|
||||
if hardware_id == "waveshare_esp32_s3_touch_lcd_2":
|
||||
# First supported device - no hardware ID in URL
|
||||
infofile = "osupdate.json"
|
||||
else:
|
||||
infofile = f"osupdate_{hardware_id}.json"
|
||||
return f"https://updates.micropythonos.com/{infofile}"
|
||||
return f"https://updates.micropythonos.com/osupdate_{hardware_id}.json"
|
||||
|
||||
async def fetch_update_info(self, hardware_id):
|
||||
"""Fetch and parse update information from server.
|
||||
|
||||
@@ -258,7 +258,7 @@ class WifiService:
|
||||
time_mod = time_module if time_module else time
|
||||
|
||||
if WifiService.is_hotspot_enabled(network_module=network_module):
|
||||
WifiService._needs_hotspot_restore = False
|
||||
WifiService._needs_hotspot_restore = True
|
||||
WifiService.disable_hotspot(network_module=network_module)
|
||||
|
||||
# Desktop mode - simulate successful connection
|
||||
|
||||
@@ -44,6 +44,11 @@ Usage in apps:
|
||||
import lvgl as lv
|
||||
import time
|
||||
|
||||
try:
|
||||
import unittest
|
||||
except ImportError: # pragma: no cover - fallback for device builds without unittest
|
||||
unittest = None
|
||||
|
||||
# Simulation globals for touch input
|
||||
_touch_x = 0
|
||||
_touch_y = 0
|
||||
@@ -51,6 +56,227 @@ _touch_pressed = False
|
||||
_touch_indev = None
|
||||
|
||||
|
||||
class GraphicalTestCase(unittest.TestCase if unittest else object):
|
||||
"""
|
||||
Base class for graphical tests.
|
||||
|
||||
Provides:
|
||||
- Automatic screen creation and cleanup
|
||||
- Common UI testing utilities
|
||||
|
||||
Class Attributes:
|
||||
SCREEN_WIDTH: Default screen width (320)
|
||||
SCREEN_HEIGHT: Default screen height (240)
|
||||
DEFAULT_RENDER_ITERATIONS: Default iterations for wait_for_render (5)
|
||||
|
||||
Instance Attributes:
|
||||
screen: The LVGL screen object for the test
|
||||
"""
|
||||
|
||||
SCREEN_WIDTH = 320
|
||||
SCREEN_HEIGHT = 240
|
||||
DEFAULT_RENDER_ITERATIONS = 5
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures before each test method."""
|
||||
self.screen = lv.obj()
|
||||
self.screen.set_size(self.SCREEN_WIDTH, self.SCREEN_HEIGHT)
|
||||
lv.screen_load(self.screen)
|
||||
self.wait_for_render()
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after each test method."""
|
||||
lv.screen_load(lv.obj())
|
||||
self.wait_for_render()
|
||||
|
||||
def wait_for_render(self, iterations=None):
|
||||
"""Wait for LVGL to render."""
|
||||
if iterations is None:
|
||||
iterations = self.DEFAULT_RENDER_ITERATIONS
|
||||
wait_for_render(iterations)
|
||||
|
||||
def find_label_with_text(self, text, parent=None):
|
||||
"""Find a label containing the specified text."""
|
||||
if parent is None:
|
||||
parent = lv.screen_active()
|
||||
return find_label_with_text(parent, text)
|
||||
|
||||
def verify_text_present(self, text, parent=None):
|
||||
"""Verify that text is present on screen."""
|
||||
if parent is None:
|
||||
parent = lv.screen_active()
|
||||
return verify_text_present(parent, text)
|
||||
|
||||
def print_screen_labels(self, parent=None):
|
||||
"""Print all labels on screen (for debugging)."""
|
||||
if parent is None:
|
||||
parent = lv.screen_active()
|
||||
print_screen_labels(parent)
|
||||
|
||||
def click_button(self, text, use_send_event=True):
|
||||
"""Click a button by its text."""
|
||||
return click_button(text, use_send_event=use_send_event)
|
||||
|
||||
def click_label(self, text, use_send_event=True):
|
||||
"""Click a label by its text."""
|
||||
return click_label(text, use_send_event=use_send_event)
|
||||
|
||||
def simulate_click(self, x, y):
|
||||
"""Simulate a click at specific coordinates."""
|
||||
simulate_click(x, y)
|
||||
self.wait_for_render()
|
||||
|
||||
def assertTextPresent(self, text, msg=None):
|
||||
"""Assert that text is present on screen."""
|
||||
if msg is None:
|
||||
msg = f"Text '{text}' not found on screen"
|
||||
self.assertTrue(self.verify_text_present(text), msg)
|
||||
|
||||
def assertTextNotPresent(self, text, msg=None):
|
||||
"""Assert that text is NOT present on screen."""
|
||||
if msg is None:
|
||||
msg = f"Text '{text}' should not be on screen"
|
||||
self.assertFalse(self.verify_text_present(text), msg)
|
||||
|
||||
|
||||
class KeyboardTestCase(GraphicalTestCase):
|
||||
"""
|
||||
Base class for keyboard tests.
|
||||
|
||||
Extends GraphicalTestCase with keyboard-specific functionality.
|
||||
|
||||
Instance Attributes:
|
||||
keyboard: The MposKeyboard instance (after create_keyboard_scene)
|
||||
textarea: The textarea widget (after create_keyboard_scene)
|
||||
"""
|
||||
|
||||
DEFAULT_RENDER_ITERATIONS = 10
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
super().setUp()
|
||||
self.keyboard = None
|
||||
self.textarea = None
|
||||
|
||||
def create_keyboard_scene(self, initial_text="", textarea_width=200, textarea_height=30):
|
||||
"""
|
||||
Create a standard keyboard test scene with textarea and keyboard.
|
||||
|
||||
Args:
|
||||
initial_text: Initial text in the textarea
|
||||
textarea_width: Width of the textarea
|
||||
textarea_height: Height of the textarea
|
||||
|
||||
Returns:
|
||||
tuple: (keyboard, textarea)
|
||||
"""
|
||||
from mpos import MposKeyboard
|
||||
|
||||
self.textarea = lv.textarea(self.screen)
|
||||
self.textarea.set_size(textarea_width, textarea_height)
|
||||
self.textarea.set_one_line(True)
|
||||
self.textarea.align(lv.ALIGN.TOP_MID, 0, 10)
|
||||
self.textarea.set_text(initial_text)
|
||||
self.wait_for_render()
|
||||
|
||||
self.keyboard = MposKeyboard(self.screen)
|
||||
self.keyboard.set_textarea(self.textarea)
|
||||
self.keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
||||
self.wait_for_render()
|
||||
|
||||
return self.keyboard, self.textarea
|
||||
|
||||
def click_keyboard_button(self, button_text):
|
||||
"""
|
||||
Click a keyboard button by its text.
|
||||
|
||||
Args:
|
||||
button_text: The text of the button to click (e.g., "q", "a", "Enter")
|
||||
|
||||
Returns:
|
||||
bool: True if button was clicked successfully
|
||||
"""
|
||||
if self.keyboard is None:
|
||||
raise RuntimeError("No keyboard created. Call create_keyboard_scene() first.")
|
||||
|
||||
return click_keyboard_button(self.keyboard, button_text)
|
||||
|
||||
def get_textarea_text(self):
|
||||
"""Get the current text in the textarea."""
|
||||
if self.textarea is None:
|
||||
raise RuntimeError("No textarea created. Call create_keyboard_scene() first.")
|
||||
return self.textarea.get_text()
|
||||
|
||||
def set_textarea_text(self, text):
|
||||
"""Set the textarea text."""
|
||||
if self.textarea is None:
|
||||
raise RuntimeError("No textarea created. Call create_keyboard_scene() first.")
|
||||
self.textarea.set_text(text)
|
||||
self.wait_for_render()
|
||||
|
||||
def clear_textarea(self):
|
||||
"""Clear the textarea."""
|
||||
self.set_textarea_text("")
|
||||
|
||||
def type_text(self, text):
|
||||
"""Type a string by clicking each character on the keyboard."""
|
||||
for char in text:
|
||||
if not self.click_keyboard_button(char):
|
||||
return False
|
||||
return True
|
||||
|
||||
def assertTextareaText(self, expected, msg=None):
|
||||
"""Assert that the textarea contains the expected text."""
|
||||
actual = self.get_textarea_text()
|
||||
if msg is None:
|
||||
msg = f"Textarea text mismatch. Expected '{expected}', got '{actual}'"
|
||||
self.assertEqual(actual, expected, msg)
|
||||
|
||||
def assertTextareaEmpty(self, msg=None):
|
||||
"""Assert that the textarea is empty."""
|
||||
if msg is None:
|
||||
msg = f"Textarea should be empty, but contains '{self.get_textarea_text()}'"
|
||||
self.assertEqual(self.get_textarea_text(), "", msg)
|
||||
|
||||
def assertTextareaContains(self, substring, msg=None):
|
||||
"""Assert that the textarea contains a substring."""
|
||||
actual = self.get_textarea_text()
|
||||
if msg is None:
|
||||
msg = f"Textarea should contain '{substring}', but has '{actual}'"
|
||||
self.assertIn(substring, actual, msg)
|
||||
|
||||
def get_keyboard_button_text(self, index):
|
||||
"""Get the text of a keyboard button by index."""
|
||||
if self.keyboard is None:
|
||||
raise RuntimeError("No keyboard created. Call create_keyboard_scene() first.")
|
||||
|
||||
try:
|
||||
return self.keyboard.get_button_text(index)
|
||||
except:
|
||||
return None
|
||||
|
||||
def find_keyboard_button_index(self, button_text):
|
||||
"""Find the index of a keyboard button by its text."""
|
||||
for i in range(100):
|
||||
text = self.get_keyboard_button_text(i)
|
||||
if text is None:
|
||||
break
|
||||
if text == button_text:
|
||||
return i
|
||||
return None
|
||||
|
||||
def get_all_keyboard_buttons(self):
|
||||
"""Get all keyboard buttons as a list of (index, text) tuples."""
|
||||
buttons = []
|
||||
for i in range(100):
|
||||
text = self.get_keyboard_button_text(i)
|
||||
if text is None:
|
||||
break
|
||||
if text:
|
||||
buttons.append((i, text))
|
||||
return buttons
|
||||
|
||||
|
||||
def wait_for_render(iterations=10):
|
||||
"""
|
||||
Wait for LVGL to process UI events and render.
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
"""
|
||||
Base test classes for MicroPythonOS testing.
|
||||
|
||||
This module provides base classes that encapsulate common test patterns:
|
||||
- GraphicalTestBase: For tests that require LVGL/UI
|
||||
- KeyboardTestBase: For tests that involve keyboard interaction
|
||||
|
||||
Usage:
|
||||
from base import GraphicalTestBase, KeyboardTestBase
|
||||
|
||||
class TestMyApp(GraphicalTestBase):
|
||||
def test_something(self):
|
||||
# self.screen is already set up
|
||||
pass
|
||||
"""
|
||||
|
||||
from .graphical_test_base import GraphicalTestBase
|
||||
from .keyboard_test_base import KeyboardTestBase
|
||||
|
||||
__all__ = [
|
||||
'GraphicalTestBase',
|
||||
'KeyboardTestBase',
|
||||
]
|
||||
@@ -1,189 +0,0 @@
|
||||
"""
|
||||
Base class for graphical tests in MicroPythonOS.
|
||||
|
||||
This class provides common setup/teardown patterns for tests that require
|
||||
LVGL/UI initialization. It handles:
|
||||
- Screen creation and cleanup
|
||||
- Common UI testing utilities
|
||||
|
||||
Usage:
|
||||
from base import GraphicalTestBase
|
||||
|
||||
class TestMyApp(GraphicalTestBase):
|
||||
def test_something(self):
|
||||
# self.screen is already set up (320x240)
|
||||
label = lv.label(self.screen)
|
||||
label.set_text("Hello")
|
||||
self.wait_for_render()
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import lvgl as lv
|
||||
|
||||
|
||||
class GraphicalTestBase(unittest.TestCase):
|
||||
"""
|
||||
Base class for all graphical tests.
|
||||
|
||||
Provides:
|
||||
- Automatic screen creation and cleanup
|
||||
- Screenshot directory configuration
|
||||
- Common UI testing utilities
|
||||
|
||||
Class Attributes:
|
||||
SCREEN_WIDTH: Default screen width (320)
|
||||
SCREEN_HEIGHT: Default screen height (240)
|
||||
DEFAULT_RENDER_ITERATIONS: Default iterations for wait_for_render (5)
|
||||
|
||||
Instance Attributes:
|
||||
screen: The LVGL screen object for the test
|
||||
"""
|
||||
|
||||
SCREEN_WIDTH = 320
|
||||
SCREEN_HEIGHT = 240
|
||||
DEFAULT_RENDER_ITERATIONS = 5
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Set up test fixtures before each test method.
|
||||
|
||||
Creates a new screen and loads it.
|
||||
"""
|
||||
# Create and load a new screen
|
||||
self.screen = lv.obj()
|
||||
self.screen.set_size(self.SCREEN_WIDTH, self.SCREEN_HEIGHT)
|
||||
lv.screen_load(self.screen)
|
||||
self.wait_for_render()
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
Clean up after each test method.
|
||||
|
||||
Loads an empty screen to clean up.
|
||||
"""
|
||||
# Load an empty screen to clean up
|
||||
lv.screen_load(lv.obj())
|
||||
self.wait_for_render()
|
||||
|
||||
def wait_for_render(self, iterations=None):
|
||||
"""
|
||||
Wait for LVGL to render.
|
||||
|
||||
Args:
|
||||
iterations: Number of render iterations (default: DEFAULT_RENDER_ITERATIONS)
|
||||
"""
|
||||
from mpos import wait_for_render
|
||||
if iterations is None:
|
||||
iterations = self.DEFAULT_RENDER_ITERATIONS
|
||||
wait_for_render(iterations)
|
||||
|
||||
def find_label_with_text(self, text, parent=None):
|
||||
"""
|
||||
Find a label containing the specified text.
|
||||
|
||||
Args:
|
||||
text: Text to search for
|
||||
parent: Parent widget to search in (default: current screen)
|
||||
|
||||
Returns:
|
||||
The label widget if found, None otherwise
|
||||
"""
|
||||
from mpos import find_label_with_text
|
||||
if parent is None:
|
||||
parent = lv.screen_active()
|
||||
return find_label_with_text(parent, text)
|
||||
|
||||
def verify_text_present(self, text, parent=None):
|
||||
"""
|
||||
Verify that text is present on screen.
|
||||
|
||||
Args:
|
||||
text: Text to search for
|
||||
parent: Parent widget to search in (default: current screen)
|
||||
|
||||
Returns:
|
||||
bool: True if text is found
|
||||
"""
|
||||
from mpos import verify_text_present
|
||||
if parent is None:
|
||||
parent = lv.screen_active()
|
||||
return verify_text_present(parent, text)
|
||||
|
||||
def print_screen_labels(self, parent=None):
|
||||
"""
|
||||
Print all labels on screen (for debugging).
|
||||
|
||||
Args:
|
||||
parent: Parent widget to search in (default: current screen)
|
||||
"""
|
||||
from mpos import print_screen_labels
|
||||
if parent is None:
|
||||
parent = lv.screen_active()
|
||||
print_screen_labels(parent)
|
||||
|
||||
def click_button(self, text, use_send_event=True):
|
||||
"""
|
||||
Click a button by its text.
|
||||
|
||||
Args:
|
||||
text: Button text to find and click
|
||||
use_send_event: If True, use send_event (more reliable)
|
||||
|
||||
Returns:
|
||||
bool: True if button was found and clicked
|
||||
"""
|
||||
from mpos import click_button
|
||||
return click_button(text, use_send_event=use_send_event)
|
||||
|
||||
def click_label(self, text, use_send_event=True):
|
||||
"""
|
||||
Click a label by its text.
|
||||
|
||||
Args:
|
||||
text: Label text to find and click
|
||||
use_send_event: If True, use send_event (more reliable)
|
||||
|
||||
Returns:
|
||||
bool: True if label was found and clicked
|
||||
"""
|
||||
from mpos import click_label
|
||||
return click_label(text, use_send_event=use_send_event)
|
||||
|
||||
def simulate_click(self, x, y):
|
||||
"""
|
||||
Simulate a click at specific coordinates.
|
||||
|
||||
Note: For most UI testing, prefer click_button() or click_label()
|
||||
which are more reliable. Use this only when testing touch behavior.
|
||||
|
||||
Args:
|
||||
x: X coordinate
|
||||
y: Y coordinate
|
||||
"""
|
||||
from mpos import simulate_click
|
||||
simulate_click(x, y)
|
||||
self.wait_for_render()
|
||||
|
||||
def assertTextPresent(self, text, msg=None):
|
||||
"""
|
||||
Assert that text is present on screen.
|
||||
|
||||
Args:
|
||||
text: Text to search for
|
||||
msg: Optional failure message
|
||||
"""
|
||||
if msg is None:
|
||||
msg = f"Text '{text}' not found on screen"
|
||||
self.assertTrue(self.verify_text_present(text), msg)
|
||||
|
||||
def assertTextNotPresent(self, text, msg=None):
|
||||
"""
|
||||
Assert that text is NOT present on screen.
|
||||
|
||||
Args:
|
||||
text: Text to search for
|
||||
msg: Optional failure message
|
||||
"""
|
||||
if msg is None:
|
||||
msg = f"Text '{text}' should not be on screen"
|
||||
self.assertFalse(self.verify_text_present(text), msg)
|
||||
@@ -1,223 +0,0 @@
|
||||
"""
|
||||
Base class for keyboard tests in MicroPythonOS.
|
||||
|
||||
This class extends GraphicalTestBase with keyboard-specific functionality:
|
||||
- Keyboard and textarea creation
|
||||
- Keyboard button clicking
|
||||
- Textarea text assertions
|
||||
|
||||
Usage:
|
||||
from base import KeyboardTestBase
|
||||
|
||||
class TestMyKeyboard(KeyboardTestBase):
|
||||
def test_typing(self):
|
||||
keyboard, textarea = self.create_keyboard_scene()
|
||||
self.click_keyboard_button("h")
|
||||
self.click_keyboard_button("i")
|
||||
self.assertTextareaText("hi")
|
||||
"""
|
||||
|
||||
import lvgl as lv
|
||||
from .graphical_test_base import GraphicalTestBase
|
||||
|
||||
|
||||
class KeyboardTestBase(GraphicalTestBase):
|
||||
"""
|
||||
Base class for keyboard tests.
|
||||
|
||||
Extends GraphicalTestBase with keyboard-specific functionality.
|
||||
|
||||
Instance Attributes:
|
||||
keyboard: The MposKeyboard instance (after create_keyboard_scene)
|
||||
textarea: The textarea widget (after create_keyboard_scene)
|
||||
"""
|
||||
|
||||
# Increase render iterations for keyboard tests
|
||||
DEFAULT_RENDER_ITERATIONS = 10
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
super().setUp()
|
||||
self.keyboard = None
|
||||
self.textarea = None
|
||||
|
||||
def create_keyboard_scene(self, initial_text="", textarea_width=200, textarea_height=30):
|
||||
"""
|
||||
Create a standard keyboard test scene with textarea and keyboard.
|
||||
|
||||
Args:
|
||||
initial_text: Initial text in the textarea
|
||||
textarea_width: Width of the textarea
|
||||
textarea_height: Height of the textarea
|
||||
|
||||
Returns:
|
||||
tuple: (keyboard, textarea)
|
||||
"""
|
||||
from mpos import MposKeyboard
|
||||
|
||||
# Create textarea
|
||||
self.textarea = lv.textarea(self.screen)
|
||||
self.textarea.set_size(textarea_width, textarea_height)
|
||||
self.textarea.set_one_line(True)
|
||||
self.textarea.align(lv.ALIGN.TOP_MID, 0, 10)
|
||||
self.textarea.set_text(initial_text)
|
||||
self.wait_for_render()
|
||||
|
||||
# Create keyboard and connect to textarea
|
||||
self.keyboard = MposKeyboard(self.screen)
|
||||
self.keyboard.set_textarea(self.textarea)
|
||||
self.keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
||||
self.wait_for_render()
|
||||
|
||||
return self.keyboard, self.textarea
|
||||
|
||||
def click_keyboard_button(self, button_text):
|
||||
"""
|
||||
Click a keyboard button by its text.
|
||||
|
||||
This uses the reliable click_keyboard_button helper which
|
||||
directly manipulates the textarea for MposKeyboard instances.
|
||||
|
||||
Args:
|
||||
button_text: The text of the button to click (e.g., "q", "a", "Enter")
|
||||
|
||||
Returns:
|
||||
bool: True if button was clicked successfully
|
||||
"""
|
||||
from mpos import click_keyboard_button
|
||||
|
||||
if self.keyboard is None:
|
||||
raise RuntimeError("No keyboard created. Call create_keyboard_scene() first.")
|
||||
|
||||
return click_keyboard_button(self.keyboard, button_text)
|
||||
|
||||
def get_textarea_text(self):
|
||||
"""
|
||||
Get the current text in the textarea.
|
||||
|
||||
Returns:
|
||||
str: The textarea text
|
||||
"""
|
||||
if self.textarea is None:
|
||||
raise RuntimeError("No textarea created. Call create_keyboard_scene() first.")
|
||||
return self.textarea.get_text()
|
||||
|
||||
def set_textarea_text(self, text):
|
||||
"""
|
||||
Set the textarea text.
|
||||
|
||||
Args:
|
||||
text: The text to set
|
||||
"""
|
||||
if self.textarea is None:
|
||||
raise RuntimeError("No textarea created. Call create_keyboard_scene() first.")
|
||||
self.textarea.set_text(text)
|
||||
self.wait_for_render()
|
||||
|
||||
def clear_textarea(self):
|
||||
"""Clear the textarea."""
|
||||
self.set_textarea_text("")
|
||||
|
||||
def type_text(self, text):
|
||||
"""
|
||||
Type a string by clicking each character on the keyboard.
|
||||
|
||||
Args:
|
||||
text: The text to type
|
||||
|
||||
Returns:
|
||||
bool: True if all characters were typed successfully
|
||||
"""
|
||||
for char in text:
|
||||
if not self.click_keyboard_button(char):
|
||||
return False
|
||||
return True
|
||||
|
||||
def assertTextareaText(self, expected, msg=None):
|
||||
"""
|
||||
Assert that the textarea contains the expected text.
|
||||
|
||||
Args:
|
||||
expected: Expected text
|
||||
msg: Optional failure message
|
||||
"""
|
||||
actual = self.get_textarea_text()
|
||||
if msg is None:
|
||||
msg = f"Textarea text mismatch. Expected '{expected}', got '{actual}'"
|
||||
self.assertEqual(actual, expected, msg)
|
||||
|
||||
def assertTextareaEmpty(self, msg=None):
|
||||
"""
|
||||
Assert that the textarea is empty.
|
||||
|
||||
Args:
|
||||
msg: Optional failure message
|
||||
"""
|
||||
if msg is None:
|
||||
msg = f"Textarea should be empty, but contains '{self.get_textarea_text()}'"
|
||||
self.assertEqual(self.get_textarea_text(), "", msg)
|
||||
|
||||
def assertTextareaContains(self, substring, msg=None):
|
||||
"""
|
||||
Assert that the textarea contains a substring.
|
||||
|
||||
Args:
|
||||
substring: Substring to search for
|
||||
msg: Optional failure message
|
||||
"""
|
||||
actual = self.get_textarea_text()
|
||||
if msg is None:
|
||||
msg = f"Textarea should contain '{substring}', but has '{actual}'"
|
||||
self.assertIn(substring, actual, msg)
|
||||
|
||||
def get_keyboard_button_text(self, index):
|
||||
"""
|
||||
Get the text of a keyboard button by index.
|
||||
|
||||
Args:
|
||||
index: Button index
|
||||
|
||||
Returns:
|
||||
str: Button text, or None if not found
|
||||
"""
|
||||
if self.keyboard is None:
|
||||
raise RuntimeError("No keyboard created. Call create_keyboard_scene() first.")
|
||||
|
||||
try:
|
||||
return self.keyboard.get_button_text(index)
|
||||
except:
|
||||
return None
|
||||
|
||||
def find_keyboard_button_index(self, button_text):
|
||||
"""
|
||||
Find the index of a keyboard button by its text.
|
||||
|
||||
Args:
|
||||
button_text: Text to search for
|
||||
|
||||
Returns:
|
||||
int: Button index, or None if not found
|
||||
"""
|
||||
for i in range(100): # Check first 100 indices
|
||||
text = self.get_keyboard_button_text(i)
|
||||
if text is None:
|
||||
break
|
||||
if text == button_text:
|
||||
return i
|
||||
return None
|
||||
|
||||
def get_all_keyboard_buttons(self):
|
||||
"""
|
||||
Get all keyboard buttons as a list of (index, text) tuples.
|
||||
|
||||
Returns:
|
||||
list: List of (index, text) tuples
|
||||
"""
|
||||
buttons = []
|
||||
for i in range(100):
|
||||
text = self.get_keyboard_button_text(i)
|
||||
if text is None:
|
||||
break
|
||||
if text: # Skip empty strings
|
||||
buttons.append((i, text))
|
||||
return buttons
|
||||
@@ -13,10 +13,10 @@ import unittest
|
||||
import lvgl as lv
|
||||
import time
|
||||
from mpos.ui.widget_animator import WidgetAnimator
|
||||
from base import KeyboardTestBase
|
||||
from mpos.ui.testing import KeyboardTestCase
|
||||
|
||||
|
||||
class TestKeyboardAnimation(KeyboardTestBase):
|
||||
class TestKeyboardAnimation(KeyboardTestCase):
|
||||
"""Test MposKeyboard compatibility with animation system."""
|
||||
|
||||
def test_keyboard_has_set_style_opa(self):
|
||||
|
||||
@@ -10,23 +10,20 @@ Usage:
|
||||
|
||||
import unittest
|
||||
import lvgl as lv
|
||||
from mpos import MposKeyboard, wait_for_render
|
||||
from mpos import MposKeyboard
|
||||
from mpos.ui.testing import GraphicalTestCase
|
||||
|
||||
|
||||
class TestDefaultVsCustomKeyboard(unittest.TestCase):
|
||||
class TestDefaultVsCustomKeyboard(GraphicalTestCase):
|
||||
"""Compare default LVGL keyboard with custom MposKeyboard."""
|
||||
|
||||
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)
|
||||
super().setUp()
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up."""
|
||||
lv.screen_load(lv.obj())
|
||||
wait_for_render(5)
|
||||
super().tearDown()
|
||||
|
||||
def test_default_lvgl_keyboard_layout(self):
|
||||
"""
|
||||
@@ -41,13 +38,13 @@ class TestDefaultVsCustomKeyboard(unittest.TestCase):
|
||||
textarea.set_size(280, 40)
|
||||
textarea.align(lv.ALIGN.TOP_MID, 0, 10)
|
||||
textarea.set_one_line(True)
|
||||
wait_for_render(5)
|
||||
self.wait_for_render(5)
|
||||
|
||||
# Create DEFAULT LVGL keyboard
|
||||
keyboard = lv.keyboard(self.screen)
|
||||
keyboard.set_textarea(textarea)
|
||||
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
||||
wait_for_render(10)
|
||||
self.wait_for_render(10)
|
||||
|
||||
print("\nDefault LVGL keyboard buttons (first 40):")
|
||||
found_special_labels = {}
|
||||
@@ -87,13 +84,13 @@ class TestDefaultVsCustomKeyboard(unittest.TestCase):
|
||||
textarea.set_size(280, 40)
|
||||
textarea.align(lv.ALIGN.TOP_MID, 0, 10)
|
||||
textarea.set_one_line(True)
|
||||
wait_for_render(5)
|
||||
self.wait_for_render(5)
|
||||
|
||||
# Create CUSTOM MposKeyboard
|
||||
keyboard = MposKeyboard(self.screen)
|
||||
keyboard.set_textarea(textarea)
|
||||
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
||||
wait_for_render(10)
|
||||
self.wait_for_render(10)
|
||||
|
||||
print("\nCustom MposKeyboard buttons (first 40):")
|
||||
found_special_labels = {}
|
||||
@@ -130,12 +127,12 @@ class TestDefaultVsCustomKeyboard(unittest.TestCase):
|
||||
textarea.set_size(280, 40)
|
||||
textarea.align(lv.ALIGN.TOP_MID, 0, 10)
|
||||
textarea.set_one_line(True)
|
||||
wait_for_render(5)
|
||||
self.wait_for_render(5)
|
||||
|
||||
keyboard = MposKeyboard(self.screen)
|
||||
keyboard.set_textarea(textarea)
|
||||
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
||||
wait_for_render(10)
|
||||
self.wait_for_render(10)
|
||||
|
||||
# Step 1: Start in lowercase
|
||||
print("\nStep 1: Initial lowercase mode")
|
||||
@@ -146,7 +143,7 @@ class TestDefaultVsCustomKeyboard(unittest.TestCase):
|
||||
# Step 2: Switch to numbers
|
||||
print("\nStep 2: Switch to numbers mode")
|
||||
keyboard.set_mode(MposKeyboard.MODE_NUMBERS)
|
||||
wait_for_render(5)
|
||||
self.wait_for_render(5)
|
||||
labels_step2 = self._get_special_labels(keyboard)
|
||||
print(f" Labels: {list(labels_step2.keys())}")
|
||||
self.assertIn("Abc", labels_step2, "Should have 'Abc' in numbers mode")
|
||||
@@ -154,7 +151,7 @@ class TestDefaultVsCustomKeyboard(unittest.TestCase):
|
||||
# Step 3: Switch back to lowercase (this is where bug might happen)
|
||||
print("\nStep 3: Switch back to lowercase via set_mode()")
|
||||
keyboard.set_mode(MposKeyboard.MODE_LOWERCASE)
|
||||
wait_for_render(5)
|
||||
self.wait_for_render(5)
|
||||
labels_step3 = self._get_special_labels(keyboard)
|
||||
print(f" Labels: {list(labels_step3.keys())}")
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@ Usage:
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from base import KeyboardTestBase
|
||||
from mpos.ui.testing import KeyboardTestCase
|
||||
|
||||
|
||||
class TestKeyboardQButton(KeyboardTestBase):
|
||||
class TestKeyboardQButton(KeyboardTestCase):
|
||||
"""Test keyboard button functionality (especially 'q' which was at index 0)."""
|
||||
|
||||
def test_q_button_works(self):
|
||||
|
||||
@@ -66,7 +66,7 @@ class TestUpdateChecker(unittest.TestCase):
|
||||
"""Test URL generation for waveshare hardware."""
|
||||
url = self.checker.get_update_url("waveshare_esp32_s3_touch_lcd_2")
|
||||
|
||||
self.assertEqual(url, "https://updates.micropythonos.com/osupdate.json")
|
||||
self.assertEqual(url, "https://updates.micropythonos.com/osupdate_waveshare_esp32_s3_touch_lcd_2.json")
|
||||
|
||||
def test_get_update_url_other_hardware(self):
|
||||
"""Test URL generation for other hardware."""
|
||||
|
||||
Reference in New Issue
Block a user