.animate helper: create and start an animation directly on a target. Preferred use pattern; closes #175
This commit is contained in:
parent
d878c8684d
commit
9ab618079a
21 changed files with 738 additions and 11 deletions
248
tests/unit/animate_method_test.py
Normal file
248
tests/unit/animate_method_test.py
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test the new .animate() shorthand method on UIDrawable and UIEntity.
|
||||
|
||||
This tests issue #177 - ergonomic animation API.
|
||||
"""
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
def test_frame_animate():
|
||||
"""Test animate() on Frame"""
|
||||
frame = mcrfpy.Frame(pos=(100, 100), size=(200, 200))
|
||||
|
||||
# Basic float property animation
|
||||
anim = frame.animate("x", 300.0, 1.0)
|
||||
assert anim is not None, "animate() should return an Animation object"
|
||||
|
||||
# Color property animation
|
||||
anim2 = frame.animate("fill_color", (255, 0, 0, 255), 0.5)
|
||||
assert anim2 is not None
|
||||
|
||||
# Vector property animation
|
||||
anim3 = frame.animate("position", (400.0, 400.0), 0.5)
|
||||
assert anim3 is not None
|
||||
|
||||
print(" Frame animate() - PASS")
|
||||
|
||||
def test_caption_animate():
|
||||
"""Test animate() on Caption"""
|
||||
caption = mcrfpy.Caption(text="Hello", pos=(50, 50))
|
||||
|
||||
# Position animation
|
||||
anim = caption.animate("y", 200.0, 1.0)
|
||||
assert anim is not None
|
||||
|
||||
# Font size animation
|
||||
anim2 = caption.animate("font_size", 24.0, 0.5)
|
||||
assert anim2 is not None
|
||||
|
||||
print(" Caption animate() - PASS")
|
||||
|
||||
def test_sprite_animate():
|
||||
"""Test animate() on Sprite"""
|
||||
# Create with default texture
|
||||
sprite = mcrfpy.Sprite(pos=(100, 100))
|
||||
|
||||
# Scale animation
|
||||
anim = sprite.animate("scale", 2.0, 1.0)
|
||||
assert anim is not None
|
||||
|
||||
# Sprite index animation
|
||||
anim2 = sprite.animate("sprite_index", 5, 0.5)
|
||||
assert anim2 is not None
|
||||
|
||||
print(" Sprite animate() - PASS")
|
||||
|
||||
def test_grid_animate():
|
||||
"""Test animate() on Grid"""
|
||||
grid = mcrfpy.Grid(grid_size=(10, 10), pos=(50, 50), size=(300, 300))
|
||||
|
||||
# Zoom animation
|
||||
anim = grid.animate("zoom", 2.0, 1.0)
|
||||
assert anim is not None
|
||||
|
||||
# Center animation
|
||||
anim2 = grid.animate("center", (100.0, 100.0), 0.5)
|
||||
assert anim2 is not None
|
||||
|
||||
print(" Grid animate() - PASS")
|
||||
|
||||
def test_entity_animate():
|
||||
"""Test animate() on Entity"""
|
||||
grid = mcrfpy.Grid(grid_size=(20, 20))
|
||||
entity = mcrfpy.Entity(grid_pos=(5, 5), grid=grid)
|
||||
|
||||
# Position animation
|
||||
anim = entity.animate("x", 10.0, 1.0)
|
||||
assert anim is not None
|
||||
|
||||
anim2 = entity.animate("y", 15.0, 1.0)
|
||||
assert anim2 is not None
|
||||
|
||||
# Sprite index animation
|
||||
anim3 = entity.animate("sprite_index", 3, 0.5)
|
||||
assert anim3 is not None
|
||||
|
||||
print(" Entity animate() - PASS")
|
||||
|
||||
def test_invalid_property_raises():
|
||||
"""Test that invalid property names raise ValueError"""
|
||||
frame = mcrfpy.Frame()
|
||||
|
||||
try:
|
||||
frame.animate("invalid_property", 100.0, 1.0)
|
||||
print(" ERROR: Should have raised ValueError for invalid property")
|
||||
return False
|
||||
except ValueError as e:
|
||||
# Should contain the property name in the error message
|
||||
if "invalid_property" in str(e):
|
||||
print(" Invalid property detection - PASS")
|
||||
return True
|
||||
else:
|
||||
print(f" ERROR: ValueError message doesn't mention property: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f" ERROR: Wrong exception type: {type(e).__name__}: {e}")
|
||||
return False
|
||||
|
||||
def test_entity_invalid_property():
|
||||
"""Test that invalid property names raise ValueError for Entity"""
|
||||
grid = mcrfpy.Grid(grid_size=(10, 10))
|
||||
entity = mcrfpy.Entity(grid=grid)
|
||||
|
||||
try:
|
||||
entity.animate("invalid_property", 100.0, 1.0)
|
||||
print(" ERROR: Should have raised ValueError for invalid Entity property")
|
||||
return False
|
||||
except ValueError as e:
|
||||
if "invalid_property" in str(e):
|
||||
print(" Entity invalid property detection - PASS")
|
||||
return True
|
||||
else:
|
||||
print(f" ERROR: ValueError message doesn't mention property: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f" ERROR: Wrong exception type: {type(e).__name__}: {e}")
|
||||
return False
|
||||
|
||||
def test_easing_options():
|
||||
"""Test various easing parameter formats"""
|
||||
frame = mcrfpy.Frame()
|
||||
|
||||
# String easing
|
||||
anim1 = frame.animate("x", 100.0, 1.0, "easeInOut")
|
||||
assert anim1 is not None, "String easing should work"
|
||||
|
||||
# Easing enum (if available)
|
||||
try:
|
||||
anim2 = frame.animate("x", 100.0, 1.0, mcrfpy.Easing.EaseIn)
|
||||
assert anim2 is not None, "Enum easing should work"
|
||||
except AttributeError:
|
||||
pass # Easing enum might not exist
|
||||
|
||||
# None for linear
|
||||
anim3 = frame.animate("x", 100.0, 1.0, None)
|
||||
assert anim3 is not None, "None easing (linear) should work"
|
||||
|
||||
print(" Easing options - PASS")
|
||||
|
||||
def test_delta_mode():
|
||||
"""Test delta=True for relative animations"""
|
||||
frame = mcrfpy.Frame(pos=(100, 100))
|
||||
|
||||
# Absolute animation (default)
|
||||
anim1 = frame.animate("x", 200.0, 1.0, delta=False)
|
||||
assert anim1 is not None
|
||||
|
||||
# Relative animation
|
||||
anim2 = frame.animate("x", 50.0, 1.0, delta=True)
|
||||
assert anim2 is not None
|
||||
|
||||
print(" Delta mode - PASS")
|
||||
|
||||
def test_callback():
|
||||
"""Test callback parameter"""
|
||||
frame = mcrfpy.Frame()
|
||||
callback_called = [False] # Use list for mutability in closure
|
||||
|
||||
def my_callback():
|
||||
callback_called[0] = True
|
||||
|
||||
anim = frame.animate("x", 100.0, 0.01, callback=my_callback)
|
||||
assert anim is not None, "Animation with callback should be created"
|
||||
|
||||
print(" Callback parameter - PASS")
|
||||
|
||||
def run_tests():
|
||||
"""Run all tests"""
|
||||
print("Testing .animate() shorthand method:")
|
||||
|
||||
all_passed = True
|
||||
|
||||
# Test UIDrawable types
|
||||
try:
|
||||
test_frame_animate()
|
||||
except Exception as e:
|
||||
print(f" Frame animate() - FAIL: {e}")
|
||||
all_passed = False
|
||||
|
||||
try:
|
||||
test_caption_animate()
|
||||
except Exception as e:
|
||||
print(f" Caption animate() - FAIL: {e}")
|
||||
all_passed = False
|
||||
|
||||
try:
|
||||
test_sprite_animate()
|
||||
except Exception as e:
|
||||
print(f" Sprite animate() - FAIL: {e}")
|
||||
all_passed = False
|
||||
|
||||
try:
|
||||
test_grid_animate()
|
||||
except Exception as e:
|
||||
print(f" Grid animate() - FAIL: {e}")
|
||||
all_passed = False
|
||||
|
||||
try:
|
||||
test_entity_animate()
|
||||
except Exception as e:
|
||||
print(f" Entity animate() - FAIL: {e}")
|
||||
all_passed = False
|
||||
|
||||
# Test property validation
|
||||
if not test_invalid_property_raises():
|
||||
all_passed = False
|
||||
|
||||
if not test_entity_invalid_property():
|
||||
all_passed = False
|
||||
|
||||
# Test optional parameters
|
||||
try:
|
||||
test_easing_options()
|
||||
except Exception as e:
|
||||
print(f" Easing options - FAIL: {e}")
|
||||
all_passed = False
|
||||
|
||||
try:
|
||||
test_delta_mode()
|
||||
except Exception as e:
|
||||
print(f" Delta mode - FAIL: {e}")
|
||||
all_passed = False
|
||||
|
||||
try:
|
||||
test_callback()
|
||||
except Exception as e:
|
||||
print(f" Callback parameter - FAIL: {e}")
|
||||
all_passed = False
|
||||
|
||||
if all_passed:
|
||||
print("\nAll tests PASSED!")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("\nSome tests FAILED!")
|
||||
sys.exit(1)
|
||||
|
||||
# Run tests immediately (no timer needed for this test)
|
||||
run_tests()
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue