Fix multiple low priority issues
closes #12, closes #80, closes #95, closes #96, closes #99 - Issue #12: Set tp_new to NULL for GridPoint and GridPointState to prevent instantiation from Python - Issue #80: Renamed Caption.size to Caption.font_size for semantic clarity - Issue #95: Fixed UICollection repr to show actual derived types instead of generic UIDrawable - Issue #96: Added extend() method to UICollection for API consistency with UIEntityCollection - Issue #99: Exposed read-only properties for Texture (sprite_width, sprite_height, sheet_width, sheet_height, sprite_count, source) and Font (family, source) All issues have corresponding tests that verify the fixes work correctly.
This commit is contained in:
parent
e5affaf317
commit
5a003a9aa5
13 changed files with 1094 additions and 7 deletions
136
tests/issue_12_gridpoint_instantiation_test.py
Normal file
136
tests/issue_12_gridpoint_instantiation_test.py
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test for Issue #12: Forbid GridPoint/GridPointState instantiation
|
||||
|
||||
This test verifies that GridPoint and GridPointState cannot be instantiated
|
||||
directly from Python, as they should only be created internally by the C++ code.
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
def test_gridpoint_instantiation():
|
||||
"""Test that GridPoint and GridPointState cannot be instantiated"""
|
||||
print("=== Testing GridPoint/GridPointState Instantiation Prevention (Issue #12) ===\n")
|
||||
|
||||
tests_passed = 0
|
||||
tests_total = 0
|
||||
|
||||
# Test 1: Try to instantiate GridPoint
|
||||
print("--- Test 1: GridPoint instantiation ---")
|
||||
tests_total += 1
|
||||
try:
|
||||
point = mcrfpy.GridPoint()
|
||||
print("✗ FAIL: GridPoint() should not be allowed")
|
||||
except TypeError as e:
|
||||
print(f"✓ PASS: GridPoint instantiation correctly prevented: {e}")
|
||||
tests_passed += 1
|
||||
except Exception as e:
|
||||
print(f"✗ FAIL: Unexpected error: {e}")
|
||||
|
||||
# Test 2: Try to instantiate GridPointState
|
||||
print("\n--- Test 2: GridPointState instantiation ---")
|
||||
tests_total += 1
|
||||
try:
|
||||
state = mcrfpy.GridPointState()
|
||||
print("✗ FAIL: GridPointState() should not be allowed")
|
||||
except TypeError as e:
|
||||
print(f"✓ PASS: GridPointState instantiation correctly prevented: {e}")
|
||||
tests_passed += 1
|
||||
except Exception as e:
|
||||
print(f"✗ FAIL: Unexpected error: {e}")
|
||||
|
||||
# Test 3: Verify GridPoint can still be obtained from Grid
|
||||
print("\n--- Test 3: GridPoint obtained from Grid.at() ---")
|
||||
tests_total += 1
|
||||
try:
|
||||
grid = mcrfpy.Grid(10, 10)
|
||||
point = grid.at(5, 5)
|
||||
print(f"✓ PASS: GridPoint obtained from Grid.at(): {point}")
|
||||
print(f" Type: {type(point).__name__}")
|
||||
tests_passed += 1
|
||||
except Exception as e:
|
||||
print(f"✗ FAIL: Could not get GridPoint from Grid: {e}")
|
||||
|
||||
# Test 4: Verify GridPointState can still be obtained from GridPoint
|
||||
print("\n--- Test 4: GridPointState obtained from GridPoint ---")
|
||||
tests_total += 1
|
||||
try:
|
||||
# GridPointState is accessed through GridPoint's click handler
|
||||
# Let's check if we can access point properties that would use GridPointState
|
||||
if hasattr(point, 'walkable'):
|
||||
print(f"✓ PASS: GridPoint has expected properties")
|
||||
print(f" walkable: {point.walkable}")
|
||||
print(f" transparent: {point.transparent}")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print("✗ FAIL: GridPoint missing expected properties")
|
||||
except Exception as e:
|
||||
print(f"✗ FAIL: Error accessing GridPoint properties: {e}")
|
||||
|
||||
# Test 5: Try to call the types directly (alternative syntax)
|
||||
print("\n--- Test 5: Alternative instantiation attempts ---")
|
||||
tests_total += 1
|
||||
all_prevented = True
|
||||
|
||||
# Try various ways to instantiate
|
||||
attempts = [
|
||||
("mcrfpy.GridPoint.__new__(mcrfpy.GridPoint)",
|
||||
lambda: mcrfpy.GridPoint.__new__(mcrfpy.GridPoint)),
|
||||
("type(point)()",
|
||||
lambda: type(point)() if 'point' in locals() else None),
|
||||
]
|
||||
|
||||
for desc, func in attempts:
|
||||
try:
|
||||
if func:
|
||||
result = func()
|
||||
print(f"✗ FAIL: {desc} should not be allowed")
|
||||
all_prevented = False
|
||||
except (TypeError, AttributeError) as e:
|
||||
print(f" ✓ Correctly prevented: {desc}")
|
||||
except Exception as e:
|
||||
print(f" ? Unexpected error for {desc}: {e}")
|
||||
|
||||
if all_prevented:
|
||||
print("✓ PASS: All alternative instantiation attempts prevented")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print("✗ FAIL: Some instantiation attempts succeeded")
|
||||
|
||||
# Summary
|
||||
print(f"\n=== SUMMARY ===")
|
||||
print(f"Tests passed: {tests_passed}/{tests_total}")
|
||||
|
||||
if tests_passed == tests_total:
|
||||
print("\nIssue #12 FIXED: GridPoint/GridPointState instantiation properly forbidden!")
|
||||
else:
|
||||
print("\nIssue #12: Some tests failed")
|
||||
|
||||
return tests_passed == tests_total
|
||||
|
||||
def run_test(runtime):
|
||||
"""Timer callback to run the test"""
|
||||
try:
|
||||
# First verify the types exist
|
||||
print("Checking that GridPoint and GridPointState types exist...")
|
||||
print(f"GridPoint type: {mcrfpy.GridPoint}")
|
||||
print(f"GridPointState type: {mcrfpy.GridPointState}")
|
||||
print()
|
||||
|
||||
success = test_gridpoint_instantiation()
|
||||
print("\nOverall result: " + ("PASS" if success else "FAIL"))
|
||||
except Exception as e:
|
||||
print(f"\nTest error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
print("\nOverall result: FAIL")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
# Set up the test scene
|
||||
mcrfpy.createScene("test")
|
||||
mcrfpy.setScene("test")
|
||||
|
||||
# Schedule test to run after game loop starts
|
||||
mcrfpy.setTimer("test", run_test, 100)
|
||||
156
tests/issue_80_caption_font_size_test.py
Normal file
156
tests/issue_80_caption_font_size_test.py
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test for Issue #80: Rename Caption.size to font_size
|
||||
|
||||
This test verifies that Caption now uses font_size property instead of size,
|
||||
while maintaining backward compatibility.
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
def test_caption_font_size():
|
||||
"""Test Caption font_size property"""
|
||||
print("=== Testing Caption font_size Property (Issue #80) ===\n")
|
||||
|
||||
tests_passed = 0
|
||||
tests_total = 0
|
||||
|
||||
# Create a caption for testing
|
||||
caption = mcrfpy.Caption((100, 100), "Test Text", mcrfpy.Font("assets/JetbrainsMono.ttf"))
|
||||
|
||||
# Test 1: Check that font_size property exists and works
|
||||
print("--- Test 1: font_size property ---")
|
||||
tests_total += 1
|
||||
try:
|
||||
# Set font size using new property name
|
||||
caption.font_size = 24
|
||||
if caption.font_size == 24:
|
||||
print("✓ PASS: font_size property works correctly")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(f"✗ FAIL: font_size is {caption.font_size}, expected 24")
|
||||
except AttributeError as e:
|
||||
print(f"✗ FAIL: font_size property not found: {e}")
|
||||
|
||||
# Test 2: Check that old 'size' property is removed
|
||||
print("\n--- Test 2: Old 'size' property removed ---")
|
||||
tests_total += 1
|
||||
try:
|
||||
# Try to access size property - this should fail
|
||||
old_size = caption.size
|
||||
print(f"✗ FAIL: 'size' property still accessible (value: {old_size}) - should be removed")
|
||||
except AttributeError:
|
||||
print("✓ PASS: 'size' property correctly removed")
|
||||
tests_passed += 1
|
||||
|
||||
# Test 3: Verify font_size changes are reflected
|
||||
print("\n--- Test 3: font_size changes ---")
|
||||
tests_total += 1
|
||||
caption.font_size = 36
|
||||
if caption.font_size == 36:
|
||||
print("✓ PASS: font_size changes are reflected correctly")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(f"✗ FAIL: font_size is {caption.font_size}, expected 36")
|
||||
|
||||
# Test 4: Check property type
|
||||
print("\n--- Test 4: Property type check ---")
|
||||
tests_total += 1
|
||||
caption.font_size = 18
|
||||
if isinstance(caption.font_size, int):
|
||||
print("✓ PASS: font_size returns integer as expected")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(f"✗ FAIL: font_size returns {type(caption.font_size).__name__}, expected int")
|
||||
|
||||
# Test 5: Verify in __dir__
|
||||
print("\n--- Test 5: Property introspection ---")
|
||||
tests_total += 1
|
||||
properties = dir(caption)
|
||||
if 'font_size' in properties:
|
||||
print("✓ PASS: 'font_size' appears in dir(caption)")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print("✗ FAIL: 'font_size' not found in dir(caption)")
|
||||
|
||||
# Check if 'size' still appears
|
||||
if 'size' in properties:
|
||||
print(" INFO: 'size' still appears in dir(caption) - backward compatibility maintained")
|
||||
else:
|
||||
print(" INFO: 'size' removed from dir(caption) - breaking change")
|
||||
|
||||
# Test 6: Edge cases
|
||||
print("\n--- Test 6: Edge cases ---")
|
||||
tests_total += 1
|
||||
all_passed = True
|
||||
|
||||
# Test setting to 0
|
||||
caption.font_size = 0
|
||||
if caption.font_size != 0:
|
||||
print(f"✗ FAIL: Setting font_size to 0 failed (got {caption.font_size})")
|
||||
all_passed = False
|
||||
|
||||
# Test setting to large value
|
||||
caption.font_size = 100
|
||||
if caption.font_size != 100:
|
||||
print(f"✗ FAIL: Setting font_size to 100 failed (got {caption.font_size})")
|
||||
all_passed = False
|
||||
|
||||
# Test float to int conversion
|
||||
caption.font_size = 24.7
|
||||
if caption.font_size != 24:
|
||||
print(f"✗ FAIL: Float to int conversion failed (got {caption.font_size})")
|
||||
all_passed = False
|
||||
|
||||
if all_passed:
|
||||
print("✓ PASS: All edge cases handled correctly")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print("✗ FAIL: Some edge cases failed")
|
||||
|
||||
# Test 7: Scene UI integration
|
||||
print("\n--- Test 7: Scene UI integration ---")
|
||||
tests_total += 1
|
||||
try:
|
||||
scene_ui = mcrfpy.sceneUI("test")
|
||||
scene_ui.append(caption)
|
||||
|
||||
# Modify font_size after adding to scene
|
||||
caption.font_size = 32
|
||||
|
||||
print("✓ PASS: Caption with font_size works in scene UI")
|
||||
tests_passed += 1
|
||||
except Exception as e:
|
||||
print(f"✗ FAIL: Scene UI integration failed: {e}")
|
||||
|
||||
# Summary
|
||||
print(f"\n=== SUMMARY ===")
|
||||
print(f"Tests passed: {tests_passed}/{tests_total}")
|
||||
|
||||
if tests_passed == tests_total:
|
||||
print("\nIssue #80 FIXED: Caption.size successfully renamed to font_size!")
|
||||
else:
|
||||
print("\nIssue #80: Some tests failed")
|
||||
|
||||
return tests_passed == tests_total
|
||||
|
||||
def run_test(runtime):
|
||||
"""Timer callback to run the test"""
|
||||
try:
|
||||
success = test_caption_font_size()
|
||||
print("\nOverall result: " + ("PASS" if success else "FAIL"))
|
||||
except Exception as e:
|
||||
print(f"\nTest error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
print("\nOverall result: FAIL")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
# Set up the test scene
|
||||
mcrfpy.createScene("test")
|
||||
mcrfpy.setScene("test")
|
||||
|
||||
# Schedule test to run after game loop starts
|
||||
mcrfpy.setTimer("test", run_test, 100)
|
||||
169
tests/issue_95_uicollection_repr_test.py
Normal file
169
tests/issue_95_uicollection_repr_test.py
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test for Issue #95: Fix UICollection __repr__ type display
|
||||
|
||||
This test verifies that UICollection's repr shows the actual types of contained
|
||||
objects instead of just showing them all as "UIDrawable".
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
def test_uicollection_repr():
|
||||
"""Test UICollection repr shows correct types"""
|
||||
print("=== Testing UICollection __repr__ Type Display (Issue #95) ===\n")
|
||||
|
||||
tests_passed = 0
|
||||
tests_total = 0
|
||||
|
||||
# Get scene UI collection
|
||||
scene_ui = mcrfpy.sceneUI("test")
|
||||
|
||||
# Test 1: Empty collection
|
||||
print("--- Test 1: Empty collection ---")
|
||||
tests_total += 1
|
||||
repr_str = repr(scene_ui)
|
||||
print(f"Empty collection repr: {repr_str}")
|
||||
if "0 objects" in repr_str:
|
||||
print("✓ PASS: Empty collection shows correctly")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print("✗ FAIL: Empty collection repr incorrect")
|
||||
|
||||
# Test 2: Add various UI elements
|
||||
print("\n--- Test 2: Mixed UI elements ---")
|
||||
tests_total += 1
|
||||
|
||||
# Add Frame
|
||||
frame = mcrfpy.Frame(10, 10, 100, 100)
|
||||
scene_ui.append(frame)
|
||||
|
||||
# Add Caption
|
||||
caption = mcrfpy.Caption((150, 50), "Test", mcrfpy.Font("assets/JetbrainsMono.ttf"))
|
||||
scene_ui.append(caption)
|
||||
|
||||
# Add Sprite
|
||||
sprite = mcrfpy.Sprite(200, 100)
|
||||
scene_ui.append(sprite)
|
||||
|
||||
# Add Grid
|
||||
grid = mcrfpy.Grid(10, 10)
|
||||
grid.x = 300
|
||||
grid.y = 100
|
||||
scene_ui.append(grid)
|
||||
|
||||
# Check repr
|
||||
repr_str = repr(scene_ui)
|
||||
print(f"Collection repr: {repr_str}")
|
||||
|
||||
# Verify it shows the correct types
|
||||
expected_types = ["1 Frame", "1 Caption", "1 Sprite", "1 Grid"]
|
||||
all_found = all(expected in repr_str for expected in expected_types)
|
||||
|
||||
if all_found and "UIDrawable" not in repr_str:
|
||||
print("✓ PASS: All types shown correctly, no generic UIDrawable")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print("✗ FAIL: Types not shown correctly")
|
||||
for expected in expected_types:
|
||||
if expected in repr_str:
|
||||
print(f" ✓ Found: {expected}")
|
||||
else:
|
||||
print(f" ✗ Missing: {expected}")
|
||||
if "UIDrawable" in repr_str:
|
||||
print(" ✗ Still shows generic UIDrawable")
|
||||
|
||||
# Test 3: Multiple of same type
|
||||
print("\n--- Test 3: Multiple objects of same type ---")
|
||||
tests_total += 1
|
||||
|
||||
# Add more frames
|
||||
frame2 = mcrfpy.Frame(10, 120, 100, 100)
|
||||
frame3 = mcrfpy.Frame(10, 230, 100, 100)
|
||||
scene_ui.append(frame2)
|
||||
scene_ui.append(frame3)
|
||||
|
||||
repr_str = repr(scene_ui)
|
||||
print(f"Collection repr: {repr_str}")
|
||||
|
||||
if "3 Frames" in repr_str:
|
||||
print("✓ PASS: Plural form shown correctly for multiple Frames")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print("✗ FAIL: Plural form not correct")
|
||||
|
||||
# Test 4: Check total count
|
||||
print("\n--- Test 4: Total count verification ---")
|
||||
tests_total += 1
|
||||
|
||||
# Should have: 3 Frames, 1 Caption, 1 Sprite, 1 Grid = 6 total
|
||||
if "6 objects:" in repr_str:
|
||||
print("✓ PASS: Total count shown correctly")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print("✗ FAIL: Total count incorrect")
|
||||
|
||||
# Test 5: Nested collections (Frame with children)
|
||||
print("\n--- Test 5: Nested collections ---")
|
||||
tests_total += 1
|
||||
|
||||
# Add child to frame
|
||||
child_sprite = mcrfpy.Sprite(10, 10)
|
||||
frame.children.append(child_sprite)
|
||||
|
||||
# Check frame's children collection
|
||||
children_repr = repr(frame.children)
|
||||
print(f"Frame children repr: {children_repr}")
|
||||
|
||||
if "1 Sprite" in children_repr:
|
||||
print("✓ PASS: Nested collection shows correct type")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print("✗ FAIL: Nested collection type incorrect")
|
||||
|
||||
# Test 6: Collection remains valid after modifications
|
||||
print("\n--- Test 6: Collection after modifications ---")
|
||||
tests_total += 1
|
||||
|
||||
# Remove an item
|
||||
scene_ui.remove(0) # Remove first frame
|
||||
|
||||
repr_str = repr(scene_ui)
|
||||
print(f"After removal repr: {repr_str}")
|
||||
|
||||
if "2 Frames" in repr_str and "5 objects:" in repr_str:
|
||||
print("✓ PASS: Collection repr updated correctly after removal")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print("✗ FAIL: Collection repr not updated correctly")
|
||||
|
||||
# Summary
|
||||
print(f"\n=== SUMMARY ===")
|
||||
print(f"Tests passed: {tests_passed}/{tests_total}")
|
||||
|
||||
if tests_passed == tests_total:
|
||||
print("\nIssue #95 FIXED: UICollection __repr__ now shows correct types!")
|
||||
else:
|
||||
print("\nIssue #95: Some tests failed")
|
||||
|
||||
return tests_passed == tests_total
|
||||
|
||||
def run_test(runtime):
|
||||
"""Timer callback to run the test"""
|
||||
try:
|
||||
success = test_uicollection_repr()
|
||||
print("\nOverall result: " + ("PASS" if success else "FAIL"))
|
||||
except Exception as e:
|
||||
print(f"\nTest error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
print("\nOverall result: FAIL")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
# Set up the test scene
|
||||
mcrfpy.createScene("test")
|
||||
mcrfpy.setScene("test")
|
||||
|
||||
# Schedule test to run after game loop starts
|
||||
mcrfpy.setTimer("test", run_test, 100)
|
||||
205
tests/issue_96_uicollection_extend_test.py
Normal file
205
tests/issue_96_uicollection_extend_test.py
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test for Issue #96: Add extend() method to UICollection
|
||||
|
||||
This test verifies that UICollection now has an extend() method similar to
|
||||
UIEntityCollection.extend().
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
def test_uicollection_extend():
|
||||
"""Test UICollection extend method"""
|
||||
print("=== Testing UICollection extend() Method (Issue #96) ===\n")
|
||||
|
||||
tests_passed = 0
|
||||
tests_total = 0
|
||||
|
||||
# Get scene UI collection
|
||||
scene_ui = mcrfpy.sceneUI("test")
|
||||
|
||||
# Test 1: Basic extend with list
|
||||
print("--- Test 1: Extend with list ---")
|
||||
tests_total += 1
|
||||
try:
|
||||
# Create a list of UI elements
|
||||
elements = [
|
||||
mcrfpy.Frame(10, 10, 100, 100),
|
||||
mcrfpy.Caption((150, 50), "Test1", mcrfpy.Font("assets/JetbrainsMono.ttf")),
|
||||
mcrfpy.Sprite(200, 100)
|
||||
]
|
||||
|
||||
# Extend the collection
|
||||
scene_ui.extend(elements)
|
||||
|
||||
if len(scene_ui) == 3:
|
||||
print("✓ PASS: Extended collection with 3 elements")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(f"✗ FAIL: Expected 3 elements, got {len(scene_ui)}")
|
||||
except Exception as e:
|
||||
print(f"✗ FAIL: Error extending with list: {e}")
|
||||
|
||||
# Test 2: Extend with tuple
|
||||
print("\n--- Test 2: Extend with tuple ---")
|
||||
tests_total += 1
|
||||
try:
|
||||
# Create a tuple of UI elements
|
||||
more_elements = (
|
||||
mcrfpy.Grid(10, 10),
|
||||
mcrfpy.Frame(300, 10, 100, 100)
|
||||
)
|
||||
|
||||
# Extend the collection
|
||||
scene_ui.extend(more_elements)
|
||||
|
||||
if len(scene_ui) == 5:
|
||||
print("✓ PASS: Extended collection with tuple (now 5 elements)")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(f"✗ FAIL: Expected 5 elements, got {len(scene_ui)}")
|
||||
except Exception as e:
|
||||
print(f"✗ FAIL: Error extending with tuple: {e}")
|
||||
|
||||
# Test 3: Extend with generator
|
||||
print("\n--- Test 3: Extend with generator ---")
|
||||
tests_total += 1
|
||||
try:
|
||||
# Create a generator of UI elements
|
||||
def create_sprites():
|
||||
for i in range(3):
|
||||
yield mcrfpy.Sprite(50 + i*50, 200)
|
||||
|
||||
# Extend with generator
|
||||
scene_ui.extend(create_sprites())
|
||||
|
||||
if len(scene_ui) == 8:
|
||||
print("✓ PASS: Extended collection with generator (now 8 elements)")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(f"✗ FAIL: Expected 8 elements, got {len(scene_ui)}")
|
||||
except Exception as e:
|
||||
print(f"✗ FAIL: Error extending with generator: {e}")
|
||||
|
||||
# Test 4: Error handling - non-iterable
|
||||
print("\n--- Test 4: Error handling - non-iterable ---")
|
||||
tests_total += 1
|
||||
try:
|
||||
scene_ui.extend(42) # Not iterable
|
||||
print("✗ FAIL: Should have raised TypeError for non-iterable")
|
||||
except TypeError as e:
|
||||
print(f"✓ PASS: Correctly raised TypeError: {e}")
|
||||
tests_passed += 1
|
||||
except Exception as e:
|
||||
print(f"✗ FAIL: Wrong exception type: {e}")
|
||||
|
||||
# Test 5: Error handling - wrong element type
|
||||
print("\n--- Test 5: Error handling - wrong element type ---")
|
||||
tests_total += 1
|
||||
try:
|
||||
scene_ui.extend([1, 2, 3]) # Wrong types
|
||||
print("✗ FAIL: Should have raised TypeError for non-UIDrawable elements")
|
||||
except TypeError as e:
|
||||
print(f"✓ PASS: Correctly raised TypeError: {e}")
|
||||
tests_passed += 1
|
||||
except Exception as e:
|
||||
print(f"✗ FAIL: Wrong exception type: {e}")
|
||||
|
||||
# Test 6: Extend empty iterable
|
||||
print("\n--- Test 6: Extend with empty list ---")
|
||||
tests_total += 1
|
||||
try:
|
||||
initial_len = len(scene_ui)
|
||||
scene_ui.extend([]) # Empty list
|
||||
|
||||
if len(scene_ui) == initial_len:
|
||||
print("✓ PASS: Extending with empty list works correctly")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(f"✗ FAIL: Length changed from {initial_len} to {len(scene_ui)}")
|
||||
except Exception as e:
|
||||
print(f"✗ FAIL: Error extending with empty list: {e}")
|
||||
|
||||
# Test 7: Z-index ordering
|
||||
print("\n--- Test 7: Z-index ordering ---")
|
||||
tests_total += 1
|
||||
try:
|
||||
# Clear and add fresh elements
|
||||
while len(scene_ui) > 0:
|
||||
scene_ui.remove(0)
|
||||
|
||||
# Add some initial elements
|
||||
frame1 = mcrfpy.Frame(0, 0, 50, 50)
|
||||
scene_ui.append(frame1)
|
||||
|
||||
# Extend with more elements
|
||||
new_elements = [
|
||||
mcrfpy.Frame(60, 0, 50, 50),
|
||||
mcrfpy.Caption((120, 25), "Test", mcrfpy.Font("assets/JetbrainsMono.ttf"))
|
||||
]
|
||||
scene_ui.extend(new_elements)
|
||||
|
||||
# Check z-indices are properly assigned
|
||||
z_indices = [scene_ui[i].z_index for i in range(3)]
|
||||
|
||||
# Z-indices should be increasing
|
||||
if z_indices[0] < z_indices[1] < z_indices[2]:
|
||||
print(f"✓ PASS: Z-indices properly ordered: {z_indices}")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(f"✗ FAIL: Z-indices not properly ordered: {z_indices}")
|
||||
except Exception as e:
|
||||
print(f"✗ FAIL: Error checking z-indices: {e}")
|
||||
|
||||
# Test 8: Extend with another UICollection
|
||||
print("\n--- Test 8: Extend with another UICollection ---")
|
||||
tests_total += 1
|
||||
try:
|
||||
# Create a Frame with children
|
||||
frame_with_children = mcrfpy.Frame(200, 200, 100, 100)
|
||||
frame_with_children.children.append(mcrfpy.Sprite(10, 10))
|
||||
frame_with_children.children.append(mcrfpy.Caption((10, 50), "Child", mcrfpy.Font("assets/JetbrainsMono.ttf")))
|
||||
|
||||
# Try to extend scene_ui with the frame's children collection
|
||||
initial_len = len(scene_ui)
|
||||
scene_ui.extend(frame_with_children.children)
|
||||
|
||||
if len(scene_ui) == initial_len + 2:
|
||||
print("✓ PASS: Extended with another UICollection")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(f"✗ FAIL: Expected {initial_len + 2} elements, got {len(scene_ui)}")
|
||||
except Exception as e:
|
||||
print(f"✗ FAIL: Error extending with UICollection: {e}")
|
||||
|
||||
# Summary
|
||||
print(f"\n=== SUMMARY ===")
|
||||
print(f"Tests passed: {tests_passed}/{tests_total}")
|
||||
|
||||
if tests_passed == tests_total:
|
||||
print("\nIssue #96 FIXED: UICollection.extend() implemented successfully!")
|
||||
else:
|
||||
print("\nIssue #96: Some tests failed")
|
||||
|
||||
return tests_passed == tests_total
|
||||
|
||||
def run_test(runtime):
|
||||
"""Timer callback to run the test"""
|
||||
try:
|
||||
success = test_uicollection_extend()
|
||||
print("\nOverall result: " + ("PASS" if success else "FAIL"))
|
||||
except Exception as e:
|
||||
print(f"\nTest error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
print("\nOverall result: FAIL")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
# Set up the test scene
|
||||
mcrfpy.createScene("test")
|
||||
mcrfpy.setScene("test")
|
||||
|
||||
# Schedule test to run after game loop starts
|
||||
mcrfpy.setTimer("test", run_test, 100)
|
||||
224
tests/issue_99_texture_font_properties_test.py
Normal file
224
tests/issue_99_texture_font_properties_test.py
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test for Issue #99: Expose Texture and Font properties
|
||||
|
||||
This test verifies that Texture and Font objects now expose their properties
|
||||
as read-only attributes.
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
def test_texture_properties():
|
||||
"""Test Texture properties"""
|
||||
print("=== Testing Texture Properties ===")
|
||||
|
||||
tests_passed = 0
|
||||
tests_total = 0
|
||||
|
||||
# Create a texture
|
||||
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
||||
|
||||
# Test 1: sprite_width property
|
||||
tests_total += 1
|
||||
try:
|
||||
width = texture.sprite_width
|
||||
if width == 16:
|
||||
print(f"✓ PASS: sprite_width = {width}")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(f"✗ FAIL: sprite_width = {width}, expected 16")
|
||||
except AttributeError as e:
|
||||
print(f"✗ FAIL: sprite_width not accessible: {e}")
|
||||
|
||||
# Test 2: sprite_height property
|
||||
tests_total += 1
|
||||
try:
|
||||
height = texture.sprite_height
|
||||
if height == 16:
|
||||
print(f"✓ PASS: sprite_height = {height}")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(f"✗ FAIL: sprite_height = {height}, expected 16")
|
||||
except AttributeError as e:
|
||||
print(f"✗ FAIL: sprite_height not accessible: {e}")
|
||||
|
||||
# Test 3: sheet_width property
|
||||
tests_total += 1
|
||||
try:
|
||||
sheet_w = texture.sheet_width
|
||||
if isinstance(sheet_w, int) and sheet_w > 0:
|
||||
print(f"✓ PASS: sheet_width = {sheet_w}")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(f"✗ FAIL: sheet_width invalid: {sheet_w}")
|
||||
except AttributeError as e:
|
||||
print(f"✗ FAIL: sheet_width not accessible: {e}")
|
||||
|
||||
# Test 4: sheet_height property
|
||||
tests_total += 1
|
||||
try:
|
||||
sheet_h = texture.sheet_height
|
||||
if isinstance(sheet_h, int) and sheet_h > 0:
|
||||
print(f"✓ PASS: sheet_height = {sheet_h}")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(f"✗ FAIL: sheet_height invalid: {sheet_h}")
|
||||
except AttributeError as e:
|
||||
print(f"✗ FAIL: sheet_height not accessible: {e}")
|
||||
|
||||
# Test 5: sprite_count property
|
||||
tests_total += 1
|
||||
try:
|
||||
count = texture.sprite_count
|
||||
expected = texture.sheet_width * texture.sheet_height
|
||||
if count == expected:
|
||||
print(f"✓ PASS: sprite_count = {count} (sheet_width * sheet_height)")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(f"✗ FAIL: sprite_count = {count}, expected {expected}")
|
||||
except AttributeError as e:
|
||||
print(f"✗ FAIL: sprite_count not accessible: {e}")
|
||||
|
||||
# Test 6: source property
|
||||
tests_total += 1
|
||||
try:
|
||||
source = texture.source
|
||||
if "kenney_tinydungeon.png" in source:
|
||||
print(f"✓ PASS: source = '{source}'")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(f"✗ FAIL: source unexpected: '{source}'")
|
||||
except AttributeError as e:
|
||||
print(f"✗ FAIL: source not accessible: {e}")
|
||||
|
||||
# Test 7: Properties are read-only
|
||||
tests_total += 1
|
||||
try:
|
||||
texture.sprite_width = 32 # Should fail
|
||||
print("✗ FAIL: sprite_width should be read-only")
|
||||
except AttributeError as e:
|
||||
print(f"✓ PASS: sprite_width is read-only: {e}")
|
||||
tests_passed += 1
|
||||
|
||||
return tests_passed, tests_total
|
||||
|
||||
def test_font_properties():
|
||||
"""Test Font properties"""
|
||||
print("\n=== Testing Font Properties ===")
|
||||
|
||||
tests_passed = 0
|
||||
tests_total = 0
|
||||
|
||||
# Create a font
|
||||
font = mcrfpy.Font("assets/JetbrainsMono.ttf")
|
||||
|
||||
# Test 1: family property
|
||||
tests_total += 1
|
||||
try:
|
||||
family = font.family
|
||||
if isinstance(family, str) and len(family) > 0:
|
||||
print(f"✓ PASS: family = '{family}'")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(f"✗ FAIL: family invalid: '{family}'")
|
||||
except AttributeError as e:
|
||||
print(f"✗ FAIL: family not accessible: {e}")
|
||||
|
||||
# Test 2: source property
|
||||
tests_total += 1
|
||||
try:
|
||||
source = font.source
|
||||
if "JetbrainsMono.ttf" in source:
|
||||
print(f"✓ PASS: source = '{source}'")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(f"✗ FAIL: source unexpected: '{source}'")
|
||||
except AttributeError as e:
|
||||
print(f"✗ FAIL: source not accessible: {e}")
|
||||
|
||||
# Test 3: Properties are read-only
|
||||
tests_total += 1
|
||||
try:
|
||||
font.family = "Arial" # Should fail
|
||||
print("✗ FAIL: family should be read-only")
|
||||
except AttributeError as e:
|
||||
print(f"✓ PASS: family is read-only: {e}")
|
||||
tests_passed += 1
|
||||
|
||||
return tests_passed, tests_total
|
||||
|
||||
def test_property_introspection():
|
||||
"""Test that properties appear in dir()"""
|
||||
print("\n=== Testing Property Introspection ===")
|
||||
|
||||
tests_passed = 0
|
||||
tests_total = 0
|
||||
|
||||
# Test Texture properties in dir()
|
||||
tests_total += 1
|
||||
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
||||
texture_props = dir(texture)
|
||||
expected_texture_props = ['sprite_width', 'sprite_height', 'sheet_width', 'sheet_height', 'sprite_count', 'source']
|
||||
|
||||
missing = [p for p in expected_texture_props if p not in texture_props]
|
||||
if not missing:
|
||||
print("✓ PASS: All Texture properties appear in dir()")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(f"✗ FAIL: Missing Texture properties in dir(): {missing}")
|
||||
|
||||
# Test Font properties in dir()
|
||||
tests_total += 1
|
||||
font = mcrfpy.Font("assets/JetbrainsMono.ttf")
|
||||
font_props = dir(font)
|
||||
expected_font_props = ['family', 'source']
|
||||
|
||||
missing = [p for p in expected_font_props if p not in font_props]
|
||||
if not missing:
|
||||
print("✓ PASS: All Font properties appear in dir()")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(f"✗ FAIL: Missing Font properties in dir(): {missing}")
|
||||
|
||||
return tests_passed, tests_total
|
||||
|
||||
def run_test(runtime):
|
||||
"""Timer callback to run the test"""
|
||||
try:
|
||||
print("=== Testing Texture and Font Properties (Issue #99) ===\n")
|
||||
|
||||
texture_passed, texture_total = test_texture_properties()
|
||||
font_passed, font_total = test_font_properties()
|
||||
intro_passed, intro_total = test_property_introspection()
|
||||
|
||||
total_passed = texture_passed + font_passed + intro_passed
|
||||
total_tests = texture_total + font_total + intro_total
|
||||
|
||||
print(f"\n=== SUMMARY ===")
|
||||
print(f"Texture tests: {texture_passed}/{texture_total}")
|
||||
print(f"Font tests: {font_passed}/{font_total}")
|
||||
print(f"Introspection tests: {intro_passed}/{intro_total}")
|
||||
print(f"Total tests passed: {total_passed}/{total_tests}")
|
||||
|
||||
if total_passed == total_tests:
|
||||
print("\nIssue #99 FIXED: Texture and Font properties exposed successfully!")
|
||||
print("\nOverall result: PASS")
|
||||
else:
|
||||
print("\nIssue #99: Some tests failed")
|
||||
print("\nOverall result: FAIL")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nTest error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
print("\nOverall result: FAIL")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
# Set up the test scene
|
||||
mcrfpy.createScene("test")
|
||||
mcrfpy.setScene("test")
|
||||
|
||||
# Schedule test to run after game loop starts
|
||||
mcrfpy.setTimer("test", run_test, 100)
|
||||
Loading…
Add table
Add a link
Reference in a new issue