BREAKING CHANGE: Hover callbacks now take only (pos) instead of (pos, button, action) - Add PyHoverCallable class for on_enter/on_exit/on_move callbacks (position-only) - Add PyCellHoverCallable class for on_cell_enter/on_cell_exit callbacks - Change UIDrawable member types from PyClickCallable to PyHoverCallable - Update PyScene::do_mouse_hover() to call hover callbacks with only position - Add tryCallPythonMethod overload for position-only subclass method calls - Update UIGrid::fireCellEnter/fireCellExit to use position-only signature - Update all tests for new callback signatures New callback signatures: | Callback | Old | New | |----------------|--------------------------|------------| | on_enter | (pos, button, action) | (pos) | | on_exit | (pos, button, action) | (pos) | | on_move | (pos, button, action) | (pos) | | on_cell_enter | (cell_pos, button, action)| (cell_pos)| | on_cell_exit | (cell_pos, button, action)| (cell_pos)| | on_click | unchanged | unchanged | | on_cell_click | unchanged | unchanged | closes #230 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
250 lines
10 KiB
Python
250 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Test unified callback signatures with enum types (#184)
|
|
|
|
This tests that all callbacks now use consistent typed arguments:
|
|
- Drawable callbacks: (pos: Vector, button: MouseButton, action: InputState)
|
|
- Grid cell callbacks: (cell_pos: Vector, button: MouseButton, action: InputState)
|
|
- Scene on_key: (key: Key, action: InputState)
|
|
"""
|
|
import mcrfpy
|
|
import sys
|
|
|
|
# Test results tracking
|
|
results = []
|
|
|
|
def test_passed(name):
|
|
results.append((name, True, None))
|
|
print(f" PASS: {name}")
|
|
|
|
def test_failed(name, error):
|
|
results.append((name, False, str(error)))
|
|
print(f" FAIL: {name}: {error}")
|
|
|
|
|
|
# ==============================================================================
|
|
# Test 1: Verify enum types exist and are accessible
|
|
# ==============================================================================
|
|
print("\n=== Callback Enum Signature Tests ===\n")
|
|
|
|
try:
|
|
# Check MouseButton enum
|
|
assert hasattr(mcrfpy, 'MouseButton'), "MouseButton enum should exist"
|
|
assert hasattr(mcrfpy.MouseButton, 'LEFT'), "MouseButton.LEFT should exist"
|
|
assert hasattr(mcrfpy.MouseButton, 'RIGHT'), "MouseButton.RIGHT should exist"
|
|
|
|
# Check InputState enum
|
|
assert hasattr(mcrfpy, 'InputState'), "InputState enum should exist"
|
|
assert hasattr(mcrfpy.InputState, 'PRESSED'), "InputState.PRESSED should exist"
|
|
assert hasattr(mcrfpy.InputState, 'RELEASED'), "InputState.RELEASED should exist"
|
|
|
|
# Check Key enum
|
|
assert hasattr(mcrfpy, 'Key'), "Key enum should exist"
|
|
assert hasattr(mcrfpy.Key, 'A'), "Key.A should exist"
|
|
assert hasattr(mcrfpy.Key, 'ESCAPE'), "Key.ESCAPE should exist"
|
|
|
|
test_passed("Enum types exist and are accessible")
|
|
except Exception as e:
|
|
test_failed("Enum types exist and are accessible", e)
|
|
|
|
# ==============================================================================
|
|
# Test 2: Grid cell callback with enum signature (subclass method)
|
|
# ==============================================================================
|
|
try:
|
|
class GridWithCellCallbacks(mcrfpy.Grid):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.cell_events = []
|
|
|
|
def on_cell_click(self, cell_pos, button, action):
|
|
# Verify types
|
|
assert isinstance(cell_pos, mcrfpy.Vector), f"cell_pos should be Vector, got {type(cell_pos)}"
|
|
self.cell_events.append(('click', cell_pos.x, cell_pos.y, button, action))
|
|
|
|
# #230 - Cell hover callbacks now only receive (cell_pos)
|
|
def on_cell_enter(self, cell_pos):
|
|
assert isinstance(cell_pos, mcrfpy.Vector), f"cell_pos should be Vector, got {type(cell_pos)}"
|
|
self.cell_events.append(('enter', cell_pos.x, cell_pos.y))
|
|
|
|
def on_cell_exit(self, cell_pos):
|
|
assert isinstance(cell_pos, mcrfpy.Vector), f"cell_pos should be Vector, got {type(cell_pos)}"
|
|
self.cell_events.append(('exit', cell_pos.x, cell_pos.y))
|
|
|
|
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
|
grid = GridWithCellCallbacks(grid_size=(5, 5), texture=texture, pos=(0, 0), size=(100, 100))
|
|
|
|
# Verify grid is subclass
|
|
assert isinstance(grid, mcrfpy.Grid), "Should be Grid instance"
|
|
assert type(grid) is not mcrfpy.Grid, "Should be subclass"
|
|
|
|
# Manually call methods to verify signature works
|
|
grid.on_cell_click(mcrfpy.Vector(1.0, 2.0), mcrfpy.MouseButton.LEFT, mcrfpy.InputState.PRESSED)
|
|
# #230 - Cell hover callbacks now only receive (cell_pos)
|
|
grid.on_cell_enter(mcrfpy.Vector(3.0, 4.0))
|
|
grid.on_cell_exit(mcrfpy.Vector(5.0, 6.0))
|
|
|
|
assert len(grid.cell_events) == 3, f"Should have 3 events, got {len(grid.cell_events)}"
|
|
assert grid.cell_events[0][0] == 'click', "First event should be click"
|
|
assert grid.cell_events[1][0] == 'enter', "Second event should be enter"
|
|
assert grid.cell_events[2][0] == 'exit', "Third event should be exit"
|
|
|
|
test_passed("Grid subclass cell callbacks with enum signature")
|
|
except Exception as e:
|
|
test_failed("Grid subclass cell callbacks with enum signature", e)
|
|
|
|
# ==============================================================================
|
|
# Test 3: Grid cell callback with property-assigned callable
|
|
# ==============================================================================
|
|
try:
|
|
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
|
grid = mcrfpy.Grid(grid_size=(5, 5), texture=texture, pos=(0, 0), size=(100, 100))
|
|
|
|
cell_events = []
|
|
|
|
def on_cell_click_handler(cell_pos, button, action):
|
|
assert isinstance(cell_pos, mcrfpy.Vector), f"cell_pos should be Vector, got {type(cell_pos)}"
|
|
cell_events.append(('click', cell_pos.x, cell_pos.y, button, action))
|
|
|
|
grid.on_cell_click = on_cell_click_handler
|
|
|
|
# Manually call to test (normally engine would call this)
|
|
grid.on_cell_click(mcrfpy.Vector(1.0, 2.0), mcrfpy.MouseButton.LEFT, mcrfpy.InputState.PRESSED)
|
|
|
|
assert len(cell_events) == 1, f"Should have 1 event, got {len(cell_events)}"
|
|
assert cell_events[0][3] == mcrfpy.MouseButton.LEFT, "Button should be MouseButton.LEFT"
|
|
assert cell_events[0][4] == mcrfpy.InputState.PRESSED, "Action should be InputState.PRESSED"
|
|
|
|
test_passed("Grid property cell callback with enum signature")
|
|
except Exception as e:
|
|
test_failed("Grid property cell callback with enum signature", e)
|
|
|
|
# ==============================================================================
|
|
# Test 4: Scene on_key callback with enum signature (subclass method)
|
|
# ==============================================================================
|
|
try:
|
|
class MyScene(mcrfpy.Scene):
|
|
def __init__(self, name):
|
|
super().__init__(name)
|
|
self.key_events = []
|
|
|
|
def on_key(self, key, action):
|
|
# Verify types - key should be Key enum, action should be InputState enum
|
|
self.key_events.append((key, action))
|
|
|
|
scene = MyScene("test_key_enum_scene")
|
|
|
|
# Manually call to test signature (normally engine would call this)
|
|
scene.on_key(mcrfpy.Key.A, mcrfpy.InputState.PRESSED)
|
|
scene.on_key(mcrfpy.Key.ESCAPE, mcrfpy.InputState.RELEASED)
|
|
|
|
assert len(scene.key_events) == 2, f"Should have 2 events, got {len(scene.key_events)}"
|
|
assert scene.key_events[0][0] == mcrfpy.Key.A, f"First key should be Key.A, got {scene.key_events[0][0]}"
|
|
assert scene.key_events[0][1] == mcrfpy.InputState.PRESSED, f"First action should be PRESSED"
|
|
assert scene.key_events[1][0] == mcrfpy.Key.ESCAPE, f"Second key should be Key.ESCAPE"
|
|
assert scene.key_events[1][1] == mcrfpy.InputState.RELEASED, f"Second action should be RELEASED"
|
|
|
|
test_passed("Scene subclass on_key with enum signature")
|
|
except Exception as e:
|
|
test_failed("Scene subclass on_key with enum signature", e)
|
|
|
|
# ==============================================================================
|
|
# Test 5: Scene on_key callback with property-assigned callable
|
|
# ==============================================================================
|
|
try:
|
|
scene = mcrfpy.Scene("test_key_prop_scene")
|
|
|
|
key_events = []
|
|
|
|
def on_key_handler(key, action):
|
|
key_events.append((key, action))
|
|
|
|
scene.on_key = on_key_handler
|
|
|
|
# Manually call to test (normally engine would call this via the callable)
|
|
scene.on_key(mcrfpy.Key.SPACE, mcrfpy.InputState.PRESSED)
|
|
|
|
assert len(key_events) == 1, f"Should have 1 event, got {len(key_events)}"
|
|
# Note: When calling directly on Python side, we pass enums directly
|
|
# The engine conversion happens internally when calling through C++
|
|
|
|
test_passed("Scene property on_key accepts enum args")
|
|
except Exception as e:
|
|
test_failed("Scene property on_key accepts enum args", e)
|
|
|
|
# ==============================================================================
|
|
# Test 6: Verify MouseButton enum values
|
|
# ==============================================================================
|
|
try:
|
|
# Verify the enum values are usable in comparisons
|
|
left = mcrfpy.MouseButton.LEFT
|
|
right = mcrfpy.MouseButton.RIGHT
|
|
|
|
assert left != right, "LEFT should not equal RIGHT"
|
|
assert left == mcrfpy.MouseButton.LEFT, "LEFT should equal itself"
|
|
|
|
# Verify we can use in conditions
|
|
def check_button(button):
|
|
if button == mcrfpy.MouseButton.LEFT:
|
|
return "left"
|
|
elif button == mcrfpy.MouseButton.RIGHT:
|
|
return "right"
|
|
return "other"
|
|
|
|
assert check_button(mcrfpy.MouseButton.LEFT) == "left"
|
|
assert check_button(mcrfpy.MouseButton.RIGHT) == "right"
|
|
|
|
test_passed("MouseButton enum values work correctly")
|
|
except Exception as e:
|
|
test_failed("MouseButton enum values work correctly", e)
|
|
|
|
# ==============================================================================
|
|
# Test 7: Verify InputState enum values
|
|
# ==============================================================================
|
|
try:
|
|
pressed = mcrfpy.InputState.PRESSED
|
|
released = mcrfpy.InputState.RELEASED
|
|
|
|
assert pressed != released, "PRESSED should not equal RELEASED"
|
|
assert pressed == mcrfpy.InputState.PRESSED, "PRESSED should equal itself"
|
|
|
|
test_passed("InputState enum values work correctly")
|
|
except Exception as e:
|
|
test_failed("InputState enum values work correctly", e)
|
|
|
|
# ==============================================================================
|
|
# Test 8: Verify Key enum values
|
|
# ==============================================================================
|
|
try:
|
|
a_key = mcrfpy.Key.A
|
|
esc_key = mcrfpy.Key.ESCAPE
|
|
|
|
assert a_key != esc_key, "A should not equal ESCAPE"
|
|
assert a_key == mcrfpy.Key.A, "A should equal itself"
|
|
|
|
# Verify various keys exist
|
|
assert hasattr(mcrfpy.Key, 'SPACE'), "SPACE should exist"
|
|
assert hasattr(mcrfpy.Key, 'ENTER'), "ENTER should exist"
|
|
assert hasattr(mcrfpy.Key, 'UP'), "UP should exist"
|
|
assert hasattr(mcrfpy.Key, 'DOWN'), "DOWN should exist"
|
|
|
|
test_passed("Key enum values work correctly")
|
|
except Exception as e:
|
|
test_failed("Key enum values work correctly", e)
|
|
|
|
# ==============================================================================
|
|
# Summary
|
|
# ==============================================================================
|
|
print("\n=== Test Summary ===")
|
|
passed = sum(1 for _, p, _ in results if p)
|
|
total = len(results)
|
|
print(f"Passed: {passed}/{total}")
|
|
|
|
if passed == total:
|
|
print("\nAll tests passed!")
|
|
sys.exit(0)
|
|
else:
|
|
print("\nSome tests failed:")
|
|
for name, p, err in results:
|
|
if not p:
|
|
print(f" - {name}: {err}")
|
|
sys.exit(1)
|