You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
Update tests
This commit is contained in:
@@ -24,6 +24,7 @@ from .mocks import (
|
||||
MockI2S,
|
||||
MockTimer,
|
||||
MockSocket,
|
||||
MockNeoPixel,
|
||||
|
||||
# MPOS mocks
|
||||
MockTaskManager,
|
||||
@@ -58,6 +59,7 @@ __all__ = [
|
||||
'MockI2S',
|
||||
'MockTimer',
|
||||
'MockSocket',
|
||||
'MockNeoPixel',
|
||||
|
||||
# MPOS mocks
|
||||
'MockTaskManager',
|
||||
|
||||
@@ -204,6 +204,50 @@ class MockTimer:
|
||||
cls._all_timers.clear()
|
||||
|
||||
|
||||
class MockNeoPixel:
|
||||
"""Mock neopixel.NeoPixel for testing LED operations."""
|
||||
|
||||
def __init__(self, pin, num_leds, bpp=3, timing=1):
|
||||
self.pin = pin
|
||||
self.num_leds = num_leds
|
||||
self.bpp = bpp
|
||||
self.timing = timing
|
||||
self.pixels = [(0, 0, 0)] * num_leds
|
||||
self.write_count = 0
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
"""Set LED color (R, G, B) or (R, G, B, W) tuple."""
|
||||
if 0 <= index < self.num_leds:
|
||||
self.pixels[index] = value
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Get LED color."""
|
||||
if 0 <= index < self.num_leds:
|
||||
return self.pixels[index]
|
||||
return (0, 0, 0)
|
||||
|
||||
def __len__(self):
|
||||
"""Return number of LEDs."""
|
||||
return self.num_leds
|
||||
|
||||
def fill(self, color):
|
||||
"""Fill all LEDs with the same color."""
|
||||
for i in range(self.num_leds):
|
||||
self.pixels[i] = color
|
||||
|
||||
def write(self):
|
||||
"""Update hardware (mock - just increment counter)."""
|
||||
self.write_count += 1
|
||||
|
||||
def get_all_colors(self):
|
||||
"""Get all LED colors (for testing assertions)."""
|
||||
return self.pixels.copy()
|
||||
|
||||
def reset_write_count(self):
|
||||
"""Reset the write counter (for testing)."""
|
||||
self.write_count = 0
|
||||
|
||||
|
||||
class MockMachine:
|
||||
"""
|
||||
Mock machine module containing all hardware mocks.
|
||||
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
# MicroPythonOS Testing Guide
|
||||
|
||||
This directory contains the test suite for MicroPythonOS. Tests can run on both desktop (for fast iteration) and on-device (for hardware verification).
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
./tests/unittest.sh
|
||||
|
||||
# Run a specific test
|
||||
./tests/unittest.sh tests/test_graphical_keyboard_q_button_bug.py
|
||||
|
||||
# Run on device
|
||||
./tests/unittest.sh tests/test_graphical_keyboard_q_button_bug.py --ondevice
|
||||
```
|
||||
|
||||
## Test Architecture
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
tests/
|
||||
├── base/ # Base test classes (DRY patterns)
|
||||
│ ├── __init__.py # Exports GraphicalTestBase, KeyboardTestBase
|
||||
│ ├── graphical_test_base.py
|
||||
│ └── keyboard_test_base.py
|
||||
├── screenshots/ # Captured screenshots for visual regression
|
||||
├── test_*.py # Test files
|
||||
├── unittest.sh # Test runner script
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
### Testing Modules
|
||||
|
||||
MicroPythonOS provides two testing modules:
|
||||
|
||||
1. **`mpos.testing`** - Hardware and system mocks
|
||||
- Location: `internal_filesystem/lib/mpos/testing/`
|
||||
- Use for: Mocking hardware (Pin, PWM, I2S, NeoPixel), network, async operations
|
||||
|
||||
2. **`mpos.ui.testing`** - LVGL/UI testing utilities
|
||||
- Location: `internal_filesystem/lib/mpos/ui/testing.py`
|
||||
- Use for: UI interaction, screenshots, widget inspection
|
||||
|
||||
## Base Test Classes
|
||||
|
||||
### GraphicalTestBase
|
||||
|
||||
Base class for all graphical (LVGL) tests. Provides:
|
||||
- Automatic screen creation/cleanup
|
||||
- Screenshot capture
|
||||
- Widget finding utilities
|
||||
- Custom assertions
|
||||
|
||||
```python
|
||||
from base import GraphicalTestBase
|
||||
|
||||
class TestMyUI(GraphicalTestBase):
|
||||
def test_something(self):
|
||||
# self.screen is already created
|
||||
label = lv.label(self.screen)
|
||||
label.set_text("Hello")
|
||||
|
||||
self.wait_for_render()
|
||||
self.assertTextPresent("Hello")
|
||||
self.capture_screenshot("my_test.raw")
|
||||
```
|
||||
|
||||
**Key Methods:**
|
||||
- `wait_for_render(iterations=5)` - Process LVGL tasks
|
||||
- `capture_screenshot(filename)` - Save screenshot
|
||||
- `find_label_with_text(text)` - Find label widget
|
||||
- `click_button(button)` - Simulate button click
|
||||
- `assertTextPresent(text)` - Assert text is on screen
|
||||
- `assertWidgetVisible(widget)` - Assert widget is visible
|
||||
|
||||
### KeyboardTestBase
|
||||
|
||||
Extends GraphicalTestBase for keyboard tests. Provides:
|
||||
- Keyboard and textarea creation
|
||||
- Reliable keyboard button clicking
|
||||
- Textarea assertions
|
||||
|
||||
```python
|
||||
from base import KeyboardTestBase
|
||||
|
||||
class TestMyKeyboard(KeyboardTestBase):
|
||||
def test_typing(self):
|
||||
self.create_keyboard_scene()
|
||||
|
||||
self.click_keyboard_button("h")
|
||||
self.click_keyboard_button("i")
|
||||
|
||||
self.assertTextareaText("hi")
|
||||
```
|
||||
|
||||
**Key Methods:**
|
||||
- `create_keyboard_scene()` - Create textarea + MposKeyboard
|
||||
- `click_keyboard_button(text)` - Click keyboard button reliably
|
||||
- `type_text(text)` - Type a string
|
||||
- `get_textarea_text()` - Get textarea content
|
||||
- `clear_textarea()` - Clear textarea
|
||||
- `assertTextareaText(expected)` - Assert textarea content
|
||||
- `assertTextareaEmpty()` - Assert textarea is empty
|
||||
|
||||
## Mock Classes
|
||||
|
||||
Import mocks from `mpos.testing`:
|
||||
|
||||
```python
|
||||
from mpos.testing import (
|
||||
# Hardware mocks
|
||||
MockMachine, # Full machine module mock
|
||||
MockPin, # GPIO pins
|
||||
MockPWM, # PWM for buzzer
|
||||
MockI2S, # Audio I2S
|
||||
MockTimer, # Hardware timers
|
||||
MockNeoPixel, # LED strips
|
||||
MockSocket, # Network sockets
|
||||
|
||||
# MPOS mocks
|
||||
MockTaskManager, # Async task management
|
||||
MockDownloadManager, # HTTP downloads
|
||||
|
||||
# Network mocks
|
||||
MockNetwork, # WiFi/network module
|
||||
MockRequests, # HTTP requests
|
||||
MockResponse, # HTTP responses
|
||||
|
||||
# Utility mocks
|
||||
MockTime, # Time functions
|
||||
MockJSON, # JSON parsing
|
||||
|
||||
# Helpers
|
||||
inject_mocks, # Inject mocks into sys.modules
|
||||
create_mock_module, # Create mock module
|
||||
)
|
||||
```
|
||||
|
||||
### Injecting Mocks
|
||||
|
||||
```python
|
||||
from mpos.testing import inject_mocks, MockMachine, MockNetwork
|
||||
|
||||
# Inject before importing modules that use hardware
|
||||
inject_mocks({
|
||||
'machine': MockMachine(),
|
||||
'network': MockNetwork(connected=True),
|
||||
})
|
||||
|
||||
# Now import the module under test
|
||||
from mpos.hardware import some_module
|
||||
```
|
||||
|
||||
### Mock Examples
|
||||
|
||||
**MockNeoPixel:**
|
||||
```python
|
||||
from mpos.testing import MockNeoPixel, MockPin
|
||||
|
||||
pin = MockPin(5)
|
||||
leds = MockNeoPixel(pin, 10)
|
||||
|
||||
leds[0] = (255, 0, 0) # Set first LED to red
|
||||
leds.write()
|
||||
|
||||
assert leds.write_count == 1
|
||||
assert leds[0] == (255, 0, 0)
|
||||
```
|
||||
|
||||
**MockRequests:**
|
||||
```python
|
||||
from mpos.testing import MockRequests
|
||||
|
||||
mock_requests = MockRequests()
|
||||
mock_requests.set_next_response(
|
||||
status_code=200,
|
||||
text='{"status": "ok"}',
|
||||
headers={'Content-Type': 'application/json'}
|
||||
)
|
||||
|
||||
response = mock_requests.get("https://api.example.com/data")
|
||||
assert response.status_code == 200
|
||||
```
|
||||
|
||||
**MockTimer:**
|
||||
```python
|
||||
from mpos.testing import MockTimer
|
||||
|
||||
timer = MockTimer(0)
|
||||
timer.init(period=1000, mode=MockTimer.PERIODIC, callback=my_callback)
|
||||
|
||||
# Manually trigger for testing
|
||||
timer.trigger()
|
||||
|
||||
# Or trigger all timers
|
||||
MockTimer.trigger_all()
|
||||
```
|
||||
|
||||
## Test Naming Conventions
|
||||
|
||||
- `test_*.py` - Standard unit tests
|
||||
- `test_graphical_*.py` - Tests requiring LVGL/UI (detected by unittest.sh)
|
||||
- `manual_test_*.py` - Manual tests (not run automatically)
|
||||
|
||||
## Writing New Tests
|
||||
|
||||
### Simple Unit Test
|
||||
|
||||
```python
|
||||
import unittest
|
||||
|
||||
class TestMyFeature(unittest.TestCase):
|
||||
def test_something(self):
|
||||
result = my_function()
|
||||
self.assertEqual(result, expected)
|
||||
```
|
||||
|
||||
### Graphical Test
|
||||
|
||||
```python
|
||||
from base import GraphicalTestBase
|
||||
import lvgl as lv
|
||||
|
||||
class TestMyUI(GraphicalTestBase):
|
||||
def test_button_click(self):
|
||||
button = lv.button(self.screen)
|
||||
label = lv.label(button)
|
||||
label.set_text("Click Me")
|
||||
|
||||
self.wait_for_render()
|
||||
self.click_button(button)
|
||||
|
||||
# Verify result
|
||||
```
|
||||
|
||||
### Keyboard Test
|
||||
|
||||
```python
|
||||
from base import KeyboardTestBase
|
||||
|
||||
class TestMyKeyboard(KeyboardTestBase):
|
||||
def test_input(self):
|
||||
self.create_keyboard_scene()
|
||||
|
||||
self.type_text("hello")
|
||||
self.assertTextareaText("hello")
|
||||
|
||||
self.click_keyboard_button("Enter")
|
||||
```
|
||||
|
||||
### Test with Mocks
|
||||
|
||||
```python
|
||||
import unittest
|
||||
from mpos.testing import MockNetwork, inject_mocks
|
||||
|
||||
class TestNetworkFeature(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.mock_network = MockNetwork(connected=True)
|
||||
inject_mocks({'network': self.mock_network})
|
||||
|
||||
def test_connected(self):
|
||||
from my_module import check_connection
|
||||
self.assertTrue(check_connection())
|
||||
|
||||
def test_disconnected(self):
|
||||
self.mock_network.set_connected(False)
|
||||
from my_module import check_connection
|
||||
self.assertFalse(check_connection())
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use base classes** - Extend `GraphicalTestBase` or `KeyboardTestBase` for UI tests
|
||||
2. **Use mpos.testing mocks** - Don't create inline mocks; use the centralized ones
|
||||
3. **Clean up in tearDown** - Base classes handle this, but custom tests should clean up
|
||||
4. **Don't include `if __name__ == '__main__'`** - The test runner handles this
|
||||
5. **Use descriptive test names** - `test_keyboard_q_button_works` not `test_1`
|
||||
6. **Add docstrings** - Explain what the test verifies and why
|
||||
|
||||
## Debugging Tests
|
||||
|
||||
```bash
|
||||
# Run with verbose output
|
||||
./tests/unittest.sh tests/test_my_test.py
|
||||
|
||||
# Run with GDB (desktop only)
|
||||
gdb --args ./lvgl_micropython/build/lvgl_micropy_unix -X heapsize=8M tests/test_my_test.py
|
||||
```
|
||||
|
||||
## Screenshots
|
||||
|
||||
Screenshots are saved to `tests/screenshots/` in raw format. Convert to PNG:
|
||||
|
||||
```bash
|
||||
cd tests/screenshots
|
||||
./convert_to_png.sh
|
||||
```
|
||||
@@ -1,102 +0,0 @@
|
||||
# Hardware Mocks for Testing AudioFlinger and LightsManager
|
||||
# Provides mock implementations of PWM, I2S, NeoPixel, and Pin classes
|
||||
|
||||
|
||||
class MockPin:
|
||||
"""Mock machine.Pin for testing."""
|
||||
|
||||
IN = 0
|
||||
OUT = 1
|
||||
PULL_UP = 2
|
||||
|
||||
def __init__(self, pin_number, mode=None, pull=None):
|
||||
self.pin_number = pin_number
|
||||
self.mode = mode
|
||||
self.pull = pull
|
||||
self._value = 0
|
||||
|
||||
def value(self, val=None):
|
||||
if val is not None:
|
||||
self._value = val
|
||||
return self._value
|
||||
|
||||
|
||||
class MockPWM:
|
||||
"""Mock machine.PWM for testing buzzer."""
|
||||
|
||||
def __init__(self, pin, freq=0, duty=0):
|
||||
self.pin = pin
|
||||
self.last_freq = freq
|
||||
self.last_duty = duty
|
||||
self.freq_history = []
|
||||
self.duty_history = []
|
||||
|
||||
def freq(self, value=None):
|
||||
"""Set or get frequency."""
|
||||
if value is not None:
|
||||
self.last_freq = value
|
||||
self.freq_history.append(value)
|
||||
return self.last_freq
|
||||
|
||||
def duty_u16(self, value=None):
|
||||
"""Set or get duty cycle (0-65535)."""
|
||||
if value is not None:
|
||||
self.last_duty = value
|
||||
self.duty_history.append(value)
|
||||
return self.last_duty
|
||||
|
||||
|
||||
class MockI2S:
|
||||
"""Mock machine.I2S for testing audio playback."""
|
||||
|
||||
TX = 0
|
||||
MONO = 1
|
||||
STEREO = 2
|
||||
|
||||
def __init__(self, id, sck, ws, sd, mode, bits, format, rate, ibuf):
|
||||
self.id = id
|
||||
self.sck = sck
|
||||
self.ws = ws
|
||||
self.sd = sd
|
||||
self.mode = mode
|
||||
self.bits = bits
|
||||
self.format = format
|
||||
self.rate = rate
|
||||
self.ibuf = ibuf
|
||||
self.written_bytes = []
|
||||
self.total_bytes_written = 0
|
||||
|
||||
def write(self, buf):
|
||||
"""Simulate writing to I2S hardware."""
|
||||
self.written_bytes.append(bytes(buf))
|
||||
self.total_bytes_written += len(buf)
|
||||
return len(buf)
|
||||
|
||||
def deinit(self):
|
||||
"""Deinitialize I2S."""
|
||||
pass
|
||||
|
||||
|
||||
class MockNeoPixel:
|
||||
"""Mock neopixel.NeoPixel for testing LEDs."""
|
||||
|
||||
def __init__(self, pin, num_leds):
|
||||
self.pin = pin
|
||||
self.num_leds = num_leds
|
||||
self.pixels = [(0, 0, 0)] * num_leds
|
||||
self.write_count = 0
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
"""Set LED color (R, G, B) tuple."""
|
||||
if 0 <= index < self.num_leds:
|
||||
self.pixels[index] = value
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Get LED color."""
|
||||
if 0 <= index < self.num_leds:
|
||||
return self.pixels[index]
|
||||
return (0, 0, 0)
|
||||
|
||||
def write(self):
|
||||
"""Update hardware (mock - just increment counter)."""
|
||||
self.write_count += 1
|
||||
@@ -13,32 +13,12 @@ import unittest
|
||||
import lvgl as lv
|
||||
import time
|
||||
import mpos.ui.anim
|
||||
from mpos.ui.keyboard import MposKeyboard
|
||||
from mpos.ui.testing import wait_for_render
|
||||
from base import KeyboardTestBase
|
||||
|
||||
class TestKeyboardAnimation(unittest.TestCase):
|
||||
|
||||
class TestKeyboardAnimation(KeyboardTestBase):
|
||||
"""Test MposKeyboard compatibility with animation system."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
# Create a test screen
|
||||
self.screen = lv.obj()
|
||||
self.screen.set_size(320, 240)
|
||||
lv.screen_load(self.screen)
|
||||
|
||||
# Create textarea
|
||||
self.textarea = lv.textarea(self.screen)
|
||||
self.textarea.set_size(280, 40)
|
||||
self.textarea.align(lv.ALIGN.TOP_MID, 0, 10)
|
||||
self.textarea.set_one_line(True)
|
||||
|
||||
print("\n=== Animation Test Setup Complete ===")
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after test."""
|
||||
lv.screen_load(lv.obj())
|
||||
print("=== Test Cleanup Complete ===\n")
|
||||
|
||||
def test_keyboard_has_set_style_opa(self):
|
||||
"""
|
||||
Test that MposKeyboard has set_style_opa method.
|
||||
@@ -47,24 +27,22 @@ class TestKeyboardAnimation(unittest.TestCase):
|
||||
"""
|
||||
print("Testing that MposKeyboard has set_style_opa...")
|
||||
|
||||
keyboard = MposKeyboard(self.screen)
|
||||
keyboard.set_textarea(self.textarea)
|
||||
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
||||
keyboard.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
self.create_keyboard_scene()
|
||||
self.keyboard.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
|
||||
# Verify method exists
|
||||
self.assertTrue(
|
||||
hasattr(keyboard, 'set_style_opa'),
|
||||
hasattr(self.keyboard, 'set_style_opa'),
|
||||
"MposKeyboard missing set_style_opa method"
|
||||
)
|
||||
self.assertTrue(
|
||||
callable(getattr(keyboard, 'set_style_opa')),
|
||||
callable(getattr(self.keyboard, 'set_style_opa')),
|
||||
"MposKeyboard.set_style_opa is not callable"
|
||||
)
|
||||
|
||||
# Try calling it (should not raise AttributeError)
|
||||
try:
|
||||
keyboard.set_style_opa(128, 0)
|
||||
self.keyboard.set_style_opa(128, 0)
|
||||
print("set_style_opa called successfully")
|
||||
except AttributeError as e:
|
||||
self.fail(f"set_style_opa raised AttributeError: {e}")
|
||||
@@ -79,15 +57,13 @@ class TestKeyboardAnimation(unittest.TestCase):
|
||||
"""
|
||||
print("Testing smooth_show animation...")
|
||||
|
||||
keyboard = MposKeyboard(self.screen)
|
||||
keyboard.set_textarea(self.textarea)
|
||||
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
||||
keyboard.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
self.create_keyboard_scene()
|
||||
self.keyboard.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
|
||||
# This should work without raising AttributeError
|
||||
try:
|
||||
mpos.ui.anim.smooth_show(keyboard)
|
||||
wait_for_render(100)
|
||||
mpos.ui.anim.smooth_show(self.keyboard)
|
||||
self.wait_for_render(100)
|
||||
print("smooth_show called successfully")
|
||||
except AttributeError as e:
|
||||
self.fail(f"smooth_show raised AttributeError: {e}\n"
|
||||
@@ -95,7 +71,7 @@ class TestKeyboardAnimation(unittest.TestCase):
|
||||
|
||||
# Verify keyboard is no longer hidden
|
||||
self.assertFalse(
|
||||
keyboard.has_flag(lv.obj.FLAG.HIDDEN),
|
||||
self.keyboard.has_flag(lv.obj.FLAG.HIDDEN),
|
||||
"Keyboard should not be hidden after smooth_show"
|
||||
)
|
||||
|
||||
@@ -109,15 +85,13 @@ class TestKeyboardAnimation(unittest.TestCase):
|
||||
"""
|
||||
print("Testing smooth_hide animation...")
|
||||
|
||||
keyboard = MposKeyboard(self.screen)
|
||||
keyboard.set_textarea(self.textarea)
|
||||
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
||||
self.create_keyboard_scene()
|
||||
# Start visible
|
||||
keyboard.remove_flag(lv.obj.FLAG.HIDDEN)
|
||||
self.keyboard.remove_flag(lv.obj.FLAG.HIDDEN)
|
||||
|
||||
# This should work without raising AttributeError
|
||||
try:
|
||||
mpos.ui.anim.smooth_hide(keyboard)
|
||||
mpos.ui.anim.smooth_hide(self.keyboard)
|
||||
print("smooth_hide called successfully")
|
||||
except AttributeError as e:
|
||||
self.fail(f"smooth_hide raised AttributeError: {e}\n"
|
||||
@@ -135,28 +109,26 @@ class TestKeyboardAnimation(unittest.TestCase):
|
||||
"""
|
||||
print("Testing full show/hide cycle...")
|
||||
|
||||
keyboard = MposKeyboard(self.screen)
|
||||
keyboard.set_textarea(self.textarea)
|
||||
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
||||
keyboard.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
self.create_keyboard_scene()
|
||||
self.keyboard.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
|
||||
# Initial state: hidden
|
||||
self.assertTrue(keyboard.has_flag(lv.obj.FLAG.HIDDEN))
|
||||
self.assertTrue(self.keyboard.has_flag(lv.obj.FLAG.HIDDEN))
|
||||
|
||||
# Show keyboard (simulates textarea click)
|
||||
try:
|
||||
mpos.ui.anim.smooth_show(keyboard)
|
||||
wait_for_render(100)
|
||||
mpos.ui.anim.smooth_show(self.keyboard)
|
||||
self.wait_for_render(100)
|
||||
except AttributeError as e:
|
||||
self.fail(f"Failed during smooth_show: {e}")
|
||||
|
||||
# Should be visible now
|
||||
self.assertFalse(keyboard.has_flag(lv.obj.FLAG.HIDDEN))
|
||||
self.assertFalse(self.keyboard.has_flag(lv.obj.FLAG.HIDDEN))
|
||||
|
||||
# Hide keyboard (simulates pressing Enter)
|
||||
try:
|
||||
mpos.ui.anim.smooth_hide(keyboard)
|
||||
wait_for_render(100)
|
||||
mpos.ui.anim.smooth_hide(self.keyboard)
|
||||
self.wait_for_render(100)
|
||||
except AttributeError as e:
|
||||
self.fail(f"Failed during smooth_hide: {e}")
|
||||
|
||||
@@ -170,22 +142,19 @@ class TestKeyboardAnimation(unittest.TestCase):
|
||||
"""
|
||||
print("Testing get_y and set_y methods...")
|
||||
|
||||
keyboard = MposKeyboard(self.screen)
|
||||
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
||||
self.create_keyboard_scene()
|
||||
|
||||
# Verify methods exist
|
||||
self.assertTrue(hasattr(keyboard, 'get_y'), "Missing get_y method")
|
||||
self.assertTrue(hasattr(keyboard, 'set_y'), "Missing set_y method")
|
||||
self.assertTrue(hasattr(self.keyboard, 'get_y'), "Missing get_y method")
|
||||
self.assertTrue(hasattr(self.keyboard, 'set_y'), "Missing set_y method")
|
||||
|
||||
# Try using them
|
||||
try:
|
||||
y = keyboard.get_y()
|
||||
keyboard.set_y(y + 10)
|
||||
new_y = keyboard.get_y()
|
||||
y = self.keyboard.get_y()
|
||||
self.keyboard.set_y(y + 10)
|
||||
new_y = self.keyboard.get_y()
|
||||
print(f"Position test: {y} -> {new_y}")
|
||||
except AttributeError as e:
|
||||
self.fail(f"Position methods raised AttributeError: {e}")
|
||||
|
||||
print("=== Position methods test PASSED ===")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user