Remove tests/base/* stuff to fix --ondevice tests

This commit is contained in:
Thomas Farstrike
2026-03-25 16:51:10 +01:00
parent b36ff9dcb9
commit 5cd3d40440
10 changed files with 246 additions and 471 deletions
@@ -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
+226
View File
@@ -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.
-23
View File
@@ -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',
]
-189
View File
@@ -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)
-223
View File
@@ -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
+2 -2
View File
@@ -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):
+1 -1
View File
@@ -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."""