249 lines
8.3 KiB
Python
249 lines
8.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Test Animation Property Locking (#120)
|
|
Verifies that multiple animations on the same property are handled correctly.
|
|
"""
|
|
|
|
import mcrfpy
|
|
import sys
|
|
|
|
print("Animation Property Locking Test Suite (#120)")
|
|
print("=" * 50)
|
|
|
|
# Test state
|
|
tests_passed = 0
|
|
tests_failed = 0
|
|
test_results = []
|
|
|
|
def test_result(name, passed, details=""):
|
|
global tests_passed, tests_failed
|
|
if passed:
|
|
tests_passed += 1
|
|
result = f"PASS: {name}"
|
|
else:
|
|
tests_failed += 1
|
|
result = f"FAIL: {name}: {details}"
|
|
print(result)
|
|
test_results.append((name, passed, details))
|
|
|
|
|
|
def test_1_replace_mode_default():
|
|
"""Test that REPLACE mode is the default and works correctly"""
|
|
try:
|
|
ui = test.children
|
|
frame = mcrfpy.Frame(pos=(100, 100), size=(100, 100))
|
|
ui.append(frame)
|
|
|
|
# Start first animation
|
|
anim1 = mcrfpy.Animation("x", 500.0, 2.0, "linear")
|
|
anim1.start(frame) # Default is replace mode
|
|
|
|
# Immediately start second animation on same property
|
|
anim2 = mcrfpy.Animation("x", 200.0, 1.0, "linear")
|
|
anim2.start(frame) # Should replace anim1
|
|
|
|
# anim1 should have been completed (jumped to final value)
|
|
# and anim2 should now be active
|
|
# The frame should be at x=500 (anim1's final value) then animating to 200
|
|
|
|
# If we got here without error, replace worked
|
|
test_result("Replace mode (default)", True)
|
|
except Exception as e:
|
|
test_result("Replace mode (default)", False, str(e))
|
|
|
|
|
|
def test_2_replace_mode_explicit():
|
|
"""Test explicit REPLACE mode"""
|
|
try:
|
|
ui = test.children
|
|
frame = mcrfpy.Frame(pos=(100, 100), size=(100, 100))
|
|
ui.append(frame)
|
|
|
|
anim1 = mcrfpy.Animation("x", 500.0, 2.0, "linear")
|
|
anim1.start(frame, conflict_mode="replace")
|
|
|
|
anim2 = mcrfpy.Animation("x", 200.0, 1.0, "linear")
|
|
anim2.start(frame, conflict_mode="replace")
|
|
|
|
test_result("Replace mode (explicit)", True)
|
|
except Exception as e:
|
|
test_result("Replace mode (explicit)", False, str(e))
|
|
|
|
|
|
def test_3_queue_mode():
|
|
"""Test QUEUE mode - animation should be queued"""
|
|
try:
|
|
ui = test.children
|
|
frame = mcrfpy.Frame(pos=(100, 100), size=(100, 100))
|
|
ui.append(frame)
|
|
|
|
# Start first animation (short duration for test)
|
|
anim1 = mcrfpy.Animation("y", 300.0, 0.5, "linear")
|
|
anim1.start(frame)
|
|
|
|
# Queue second animation
|
|
anim2 = mcrfpy.Animation("y", 100.0, 0.5, "linear")
|
|
anim2.start(frame, conflict_mode="queue")
|
|
|
|
# Both should be accepted without error
|
|
# anim2 will start after anim1 completes
|
|
test_result("Queue mode", True)
|
|
except Exception as e:
|
|
test_result("Queue mode", False, str(e))
|
|
|
|
|
|
def test_4_error_mode():
|
|
"""Test ERROR mode - should raise RuntimeError"""
|
|
try:
|
|
ui = test.children
|
|
frame = mcrfpy.Frame(pos=(100, 100), size=(100, 100))
|
|
ui.append(frame)
|
|
|
|
anim1 = mcrfpy.Animation("w", 200.0, 2.0, "linear")
|
|
anim1.start(frame)
|
|
|
|
# Try to start second animation with error mode
|
|
anim2 = mcrfpy.Animation("w", 300.0, 1.0, "linear")
|
|
try:
|
|
anim2.start(frame, conflict_mode="error")
|
|
test_result("Error mode", False, "Expected RuntimeError but none was raised")
|
|
except RuntimeError as e:
|
|
# This is expected!
|
|
if "conflict" in str(e).lower() or "already" in str(e).lower():
|
|
test_result("Error mode", True)
|
|
else:
|
|
test_result("Error mode", False, f"Wrong error message: {e}")
|
|
except Exception as e:
|
|
test_result("Error mode", False, str(e))
|
|
|
|
|
|
def test_5_invalid_conflict_mode():
|
|
"""Test that invalid conflict_mode raises ValueError"""
|
|
try:
|
|
ui = test.children
|
|
frame = mcrfpy.Frame(pos=(100, 100), size=(100, 100))
|
|
ui.append(frame)
|
|
|
|
anim = mcrfpy.Animation("h", 200.0, 1.0, "linear")
|
|
try:
|
|
anim.start(frame, conflict_mode="invalid_mode")
|
|
test_result("Invalid conflict_mode", False, "Expected ValueError but none raised")
|
|
except ValueError as e:
|
|
if "invalid" in str(e).lower():
|
|
test_result("Invalid conflict_mode", True)
|
|
else:
|
|
test_result("Invalid conflict_mode", False, f"Wrong error: {e}")
|
|
except Exception as e:
|
|
test_result("Invalid conflict_mode", False, str(e))
|
|
|
|
|
|
def test_6_different_properties_no_conflict():
|
|
"""Test that different properties can animate simultaneously"""
|
|
try:
|
|
ui = test.children
|
|
frame = mcrfpy.Frame(pos=(100, 100), size=(100, 100))
|
|
ui.append(frame)
|
|
|
|
# Animate different properties - should not conflict
|
|
anim_x = mcrfpy.Animation("x", 500.0, 1.0, "linear")
|
|
anim_y = mcrfpy.Animation("y", 500.0, 1.0, "linear")
|
|
anim_w = mcrfpy.Animation("w", 200.0, 1.0, "linear")
|
|
|
|
anim_x.start(frame, conflict_mode="error")
|
|
anim_y.start(frame, conflict_mode="error")
|
|
anim_w.start(frame, conflict_mode="error")
|
|
|
|
# All should succeed without error since they're different properties
|
|
test_result("Different properties no conflict", True)
|
|
except RuntimeError as e:
|
|
test_result("Different properties no conflict", False, f"Unexpected conflict: {e}")
|
|
except Exception as e:
|
|
test_result("Different properties no conflict", False, str(e))
|
|
|
|
|
|
def test_7_different_targets_no_conflict():
|
|
"""Test that same property on different targets doesn't conflict"""
|
|
try:
|
|
ui = test.children
|
|
frame1 = mcrfpy.Frame(pos=(100, 100), size=(100, 100))
|
|
frame2 = mcrfpy.Frame(pos=(200, 200), size=(100, 100))
|
|
ui.append(frame1)
|
|
ui.append(frame2)
|
|
|
|
# Same property, different targets - should not conflict
|
|
anim1 = mcrfpy.Animation("x", 500.0, 1.0, "linear")
|
|
anim2 = mcrfpy.Animation("x", 600.0, 1.0, "linear")
|
|
|
|
anim1.start(frame1, conflict_mode="error")
|
|
anim2.start(frame2, conflict_mode="error")
|
|
|
|
test_result("Different targets no conflict", True)
|
|
except RuntimeError as e:
|
|
test_result("Different targets no conflict", False, f"Unexpected conflict: {e}")
|
|
except Exception as e:
|
|
test_result("Different targets no conflict", False, str(e))
|
|
|
|
|
|
def test_8_replace_completes_old():
|
|
"""Test that REPLACE mode completes the old animation's value"""
|
|
try:
|
|
ui = test.children
|
|
frame = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
|
|
ui.append(frame)
|
|
|
|
# Start animation to move x to 500
|
|
anim1 = mcrfpy.Animation("x", 500.0, 10.0, "linear") # Long duration
|
|
anim1.start(frame)
|
|
|
|
# Immediately replace - should complete anim1 (jump to 500)
|
|
anim2 = mcrfpy.Animation("x", 200.0, 1.0, "linear")
|
|
anim2.start(frame, conflict_mode="replace")
|
|
|
|
# Frame should now be at x=500 (anim1's final) and animating to 200
|
|
# Due to immediate completion, x should equal 500 right now
|
|
if frame.x == 500.0:
|
|
test_result("Replace completes old animation", True)
|
|
else:
|
|
test_result("Replace completes old animation", False,
|
|
f"Expected x=500, got x={frame.x}")
|
|
except Exception as e:
|
|
test_result("Replace completes old animation", False, str(e))
|
|
|
|
|
|
def run_all_tests(runtime):
|
|
"""Run all property locking tests"""
|
|
print("\nRunning Animation Property Locking Tests...")
|
|
print("-" * 50)
|
|
|
|
test_1_replace_mode_default()
|
|
test_2_replace_mode_explicit()
|
|
test_3_queue_mode()
|
|
test_4_error_mode()
|
|
test_5_invalid_conflict_mode()
|
|
test_6_different_properties_no_conflict()
|
|
test_7_different_targets_no_conflict()
|
|
test_8_replace_completes_old()
|
|
|
|
# Print results
|
|
print("\n" + "=" * 50)
|
|
print(f"Tests passed: {tests_passed}")
|
|
print(f"Tests failed: {tests_failed}")
|
|
|
|
if tests_failed == 0:
|
|
print("\nAll tests passed!")
|
|
else:
|
|
print(f"\n{tests_failed} tests failed:")
|
|
for name, passed, details in test_results:
|
|
if not passed:
|
|
print(f" - {name}: {details}")
|
|
|
|
# Exit with appropriate code
|
|
sys.exit(0 if tests_failed == 0 else 1)
|
|
|
|
|
|
# Setup and run
|
|
test = mcrfpy.Scene("test")
|
|
test.activate()
|
|
|
|
# Start tests after a brief delay to allow scene to initialize
|
|
mcrfpy.setTimer("start", run_all_tests, 100)
|