Organize test suite: add README, move loose tests to proper directories

- Add tests/README.md documenting test structure and usage
- Move issue_*_test.py files to tests/regression/ (9 files)
- Move loose test_*.py files to tests/unit/ (18 files)
- tests/ root now contains only pytest infrastructure

Addresses #166

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
John McCardle 2026-01-21 21:34:22 -05:00
commit 165db91b8d
28 changed files with 174 additions and 0 deletions

View file

@ -0,0 +1,181 @@
#!/usr/bin/env python3
"""Test for issue #176: Entity position naming consistency.
Tests the new Entity position properties:
- pos, x, y: pixel coordinates (requires grid attachment)
- grid_pos, grid_x, grid_y: integer tile coordinates
- draw_pos: fractional tile coordinates for animation
"""
import mcrfpy
import sys
def test_entity_positions():
"""Test Entity position properties with grid attachment."""
errors = []
# Create a texture with 16x16 sprites (standard tile size)
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
# Create a grid (10x10 tiles, 16x16 pixels each)
grid = mcrfpy.Grid(grid_size=(10, 10), texture=texture, pos=(0, 0), size=(160, 160))
# Create entity at tile position (3, 5)
entity = mcrfpy.Entity(grid_pos=(3, 5), texture=texture, grid=grid)
# Test 1: grid_pos should return integer tile coordinates
gpos = entity.grid_pos
if gpos.x != 3 or gpos.y != 5:
errors.append(f"grid_pos: expected (3, 5), got ({gpos.x}, {gpos.y})")
# Test 2: grid_x and grid_y should return integers
if entity.grid_x != 3:
errors.append(f"grid_x: expected 3, got {entity.grid_x}")
if entity.grid_y != 5:
errors.append(f"grid_y: expected 5, got {entity.grid_y}")
# Test 3: draw_pos should return float tile coordinates
dpos = entity.draw_pos
if abs(dpos.x - 3.0) > 0.001 or abs(dpos.y - 5.0) > 0.001:
errors.append(f"draw_pos: expected (3.0, 5.0), got ({dpos.x}, {dpos.y})")
# Test 4: pos should return pixel coordinates (tile * tile_size)
# With 16x16 tiles: (3, 5) tiles = (48, 80) pixels
ppos = entity.pos
if abs(ppos.x - 48.0) > 0.001 or abs(ppos.y - 80.0) > 0.001:
errors.append(f"pos: expected (48.0, 80.0), got ({ppos.x}, {ppos.y})")
# Test 5: x and y should return pixel coordinates
if abs(entity.x - 48.0) > 0.001:
errors.append(f"x: expected 48.0, got {entity.x}")
if abs(entity.y - 80.0) > 0.001:
errors.append(f"y: expected 80.0, got {entity.y}")
# Test 6: Setting grid_x/grid_y should update position
entity.grid_x = 7
entity.grid_y = 2
if entity.grid_x != 7 or entity.grid_y != 2:
errors.append(f"After setting grid_x/y: expected (7, 2), got ({entity.grid_x}, {entity.grid_y})")
# Pixel should update too: (7, 2) * 16 = (112, 32)
if abs(entity.x - 112.0) > 0.001 or abs(entity.y - 32.0) > 0.001:
errors.append(f"After grid_x/y set, pixel pos: expected (112, 32), got ({entity.x}, {entity.y})")
# Test 7: Setting pos (pixels) should update grid position
entity.pos = mcrfpy.Vector(64, 96) # (64, 96) / 16 = (4, 6) tiles
if abs(entity.draw_pos.x - 4.0) > 0.001 or abs(entity.draw_pos.y - 6.0) > 0.001:
errors.append(f"After setting pos, draw_pos: expected (4, 6), got ({entity.draw_pos.x}, {entity.draw_pos.y})")
if entity.grid_x != 4 or entity.grid_y != 6:
errors.append(f"After setting pos, grid_x/y: expected (4, 6), got ({entity.grid_x}, {entity.grid_y})")
# Test 8: repr should show grid_x/grid_y
repr_str = repr(entity)
if "grid_x=" not in repr_str or "grid_y=" not in repr_str:
errors.append(f"repr should contain grid_x/grid_y: {repr_str}")
return errors
def test_entity_without_grid():
"""Test that pixel positions require grid attachment."""
errors = []
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
entity = mcrfpy.Entity(grid_pos=(3, 5), texture=texture) # No grid
# grid_pos should work without grid
if entity.grid_x != 3 or entity.grid_y != 5:
errors.append(f"grid_x/y without grid: expected (3, 5), got ({entity.grid_x}, {entity.grid_y})")
# pos should raise RuntimeError without grid
try:
_ = entity.pos
errors.append("entity.pos should raise RuntimeError without grid")
except RuntimeError as e:
if "not attached to a Grid" not in str(e):
errors.append(f"Wrong error message for pos: {e}")
# x should raise RuntimeError without grid
try:
_ = entity.x
errors.append("entity.x should raise RuntimeError without grid")
except RuntimeError as e:
if "not attached to a Grid" not in str(e):
errors.append(f"Wrong error message for x: {e}")
# Setting pos should raise RuntimeError without grid
try:
entity.pos = mcrfpy.Vector(100, 100)
errors.append("setting entity.pos should raise RuntimeError without grid")
except RuntimeError as e:
if "not attached to a Grid" not in str(e):
errors.append(f"Wrong error message for setting pos: {e}")
return errors
def test_animation_properties():
"""Test that animation properties work correctly."""
errors = []
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
grid = mcrfpy.Grid(grid_size=(10, 10), texture=texture, pos=(0, 0), size=(160, 160))
entity = mcrfpy.Entity(grid_pos=(0, 0), texture=texture, grid=grid)
# Test draw_x/draw_y animation properties exist
try:
# hasProperty should accept draw_x and draw_y
# We can't call hasProperty directly, but we can try to animate
# and check if it raises ValueError for invalid property
pass # Animation tested implicitly through animate() error handling
except Exception as e:
errors.append(f"Animation property test failed: {e}")
return errors
def main():
print("Testing issue #176: Entity position naming consistency")
print("=" * 60)
all_errors = []
# Test 1: Entity with grid
print("\n1. Testing entity positions with grid attachment...")
errors = test_entity_positions()
if errors:
for e in errors:
print(f" FAIL: {e}")
all_errors.extend(errors)
else:
print(" PASS")
# Test 2: Entity without grid
print("\n2. Testing entity positions without grid...")
errors = test_entity_without_grid()
if errors:
for e in errors:
print(f" FAIL: {e}")
all_errors.extend(errors)
else:
print(" PASS")
# Test 3: Animation properties
print("\n3. Testing animation properties...")
errors = test_animation_properties()
if errors:
for e in errors:
print(f" FAIL: {e}")
all_errors.extend(errors)
else:
print(" PASS")
print("\n" + "=" * 60)
if all_errors:
print(f"FAILED: {len(all_errors)} error(s)")
sys.exit(1)
else:
print("All tests passed!")
sys.exit(0)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,83 @@
#!/usr/bin/env python3
"""Test for issue #177: GridPoint.grid_pos property
Verifies that GridPoint objects have a grid_pos property that returns
the (grid_x, grid_y) coordinates as a tuple.
"""
import mcrfpy
import sys
print("Starting test...")
# Create a simple grid without texture (should work in headless mode)
grid = mcrfpy.Grid(grid_x=10, grid_y=8)
print(f"Created grid: {grid}")
# Test various grid positions
test_cases = [
(0, 0),
(5, 3),
(9, 7),
(0, 7),
(9, 0),
]
all_passed = True
for x, y in test_cases:
point = grid.at(x, y)
print(f"Got point at ({x}, {y}): {point}")
# Check that grid_pos property exists and returns correct value
if not hasattr(point, 'grid_pos'):
print(f"FAIL: GridPoint at ({x}, {y}) has no 'grid_pos' attribute")
all_passed = False
continue
grid_pos = point.grid_pos
# Verify it's a tuple
if not isinstance(grid_pos, tuple):
print(f"FAIL: grid_pos is {type(grid_pos).__name__}, expected tuple")
all_passed = False
continue
# Verify it has correct length
if len(grid_pos) != 2:
print(f"FAIL: grid_pos has length {len(grid_pos)}, expected 2")
all_passed = False
continue
# Verify correct values
if grid_pos != (x, y):
print(f"FAIL: grid_pos = {grid_pos}, expected ({x}, {y})")
all_passed = False
continue
print(f"OK: GridPoint at ({x}, {y}) has grid_pos = {grid_pos}")
# Test that grid_pos is read-only (should raise AttributeError)
point = grid.at(2, 3)
try:
point.grid_pos = (5, 5)
print("FAIL: grid_pos should be read-only but allowed assignment")
all_passed = False
except AttributeError:
print("OK: grid_pos is read-only (raises AttributeError on assignment)")
except Exception as e:
print(f"FAIL: Unexpected exception on assignment: {type(e).__name__}: {e}")
all_passed = False
# Verify the repr includes the coordinates
point = grid.at(4, 6)
repr_str = repr(point)
if "(4, 6)" in repr_str:
print(f"OK: repr includes coordinates: {repr_str}")
else:
print(f"Note: repr format: {repr_str}")
if all_passed:
print("PASS")
sys.exit(0)
else:
print("FAIL")
sys.exit(1)

View file

@ -0,0 +1,113 @@
#!/usr/bin/env python3
"""Test for issues #179 and #181: Grid attributes return Vectors and grid_x/grid_y renamed to grid_w/grid_h"""
import mcrfpy
import sys
def test_grid_vectors():
"""Test that Grid attributes return Vector objects instead of tuples."""
print("Testing issue #179: Grid attributes should return Vectors...")
# Create a Grid for testing
grid = mcrfpy.Grid(pos=(100, 150), size=(400, 300), grid_size=(20, 15))
# Test grid.size returns a Vector
size = grid.size
print(f" grid.size = {size}")
assert hasattr(size, 'x'), f"grid.size should have .x attribute, got {type(size)}"
assert hasattr(size, 'y'), f"grid.size should have .y attribute, got {type(size)}"
assert size.x == 400.0, f"grid.size.x should be 400.0, got {size.x}"
assert size.y == 300.0, f"grid.size.y should be 300.0, got {size.y}"
print(" PASS: grid.size returns Vector")
# Test grid.grid_size returns a Vector
grid_size = grid.grid_size
print(f" grid.grid_size = {grid_size}")
assert hasattr(grid_size, 'x'), f"grid.grid_size should have .x attribute, got {type(grid_size)}"
assert hasattr(grid_size, 'y'), f"grid.grid_size should have .y attribute, got {type(grid_size)}"
assert grid_size.x == 20.0, f"grid.grid_size.x should be 20.0, got {grid_size.x}"
assert grid_size.y == 15.0, f"grid.grid_size.y should be 15.0, got {grid_size.y}"
print(" PASS: grid.grid_size returns Vector")
# Test grid.center returns a Vector
grid.center = (50.0, 75.0) # Set center first
center = grid.center
print(f" grid.center = {center}")
assert hasattr(center, 'x'), f"grid.center should have .x attribute, got {type(center)}"
assert hasattr(center, 'y'), f"grid.center should have .y attribute, got {type(center)}"
assert center.x == 50.0, f"grid.center.x should be 50.0, got {center.x}"
assert center.y == 75.0, f"grid.center.y should be 75.0, got {center.y}"
print(" PASS: grid.center returns Vector")
# Test grid.position returns a Vector
position = grid.position
print(f" grid.position = {position}")
assert hasattr(position, 'x'), f"grid.position should have .x attribute, got {type(position)}"
assert hasattr(position, 'y'), f"grid.position should have .y attribute, got {type(position)}"
assert position.x == 100.0, f"grid.position.x should be 100.0, got {position.x}"
assert position.y == 150.0, f"grid.position.y should be 150.0, got {position.y}"
print(" PASS: grid.position returns Vector")
print("Issue #179 tests PASSED!")
def test_grid_w_h():
"""Test that grid_w and grid_h exist and grid_x/grid_y do not."""
print("\nTesting issue #181: grid_x/grid_y renamed to grid_w/grid_h...")
grid = mcrfpy.Grid(grid_size=(25, 18))
# Test grid_w and grid_h exist and return correct values
grid_w = grid.grid_w
grid_h = grid.grid_h
print(f" grid.grid_w = {grid_w}")
print(f" grid.grid_h = {grid_h}")
assert grid_w == 25, f"grid.grid_w should be 25, got {grid_w}"
assert grid_h == 18, f"grid.grid_h should be 18, got {grid_h}"
print(" PASS: grid.grid_w and grid.grid_h exist and return correct values")
# Test grid_x and grid_y do NOT exist (AttributeError expected)
try:
_ = grid.grid_x
print(" FAIL: grid.grid_x should not exist but it does!")
sys.exit(1)
except AttributeError:
print(" PASS: grid.grid_x correctly raises AttributeError")
try:
_ = grid.grid_y
print(" FAIL: grid.grid_y should not exist but it does!")
sys.exit(1)
except AttributeError:
print(" PASS: grid.grid_y correctly raises AttributeError")
print("Issue #181 tests PASSED!")
def main():
"""Run all tests."""
print("=" * 60)
print("Testing Grid Vector attributes and grid_w/grid_h rename")
print("=" * 60)
try:
test_grid_vectors()
test_grid_w_h()
print("\n" + "=" * 60)
print("ALL TESTS PASSED!")
print("=" * 60)
sys.exit(0)
except AssertionError as e:
print(f"\nFAIL: {e}")
sys.exit(1)
except Exception as e:
print(f"\nERROR: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,75 @@
"""Test for issue #180: Timers without a user-stored reference should still fire.
This test verifies that timers continue running even when the Python object
goes out of scope, and that they can be accessed via mcrfpy.timers.
"""
import mcrfpy
import gc
import sys
# Track timer fires
fire_count = 0
def on_timer(timer, runtime):
"""Timer callback that increments fire count."""
global fire_count
fire_count += 1
print(f"Timer fired! count={fire_count}, runtime={runtime}")
def create_orphan_timer():
"""Create a timer without storing a reference."""
# This timer should keep running even though we don't store the reference
mcrfpy.Timer("orphan_timer", on_timer, 50) # 50ms interval
print("Created orphan timer (no reference stored)")
# Set up test scene
scene = mcrfpy.Scene("test")
mcrfpy.current_scene = scene
# Create the orphan timer (no reference stored)
create_orphan_timer()
# Force garbage collection to ensure the Python wrapper is collected
gc.collect()
print("Forced garbage collection")
# Check timers immediately after GC
timers = mcrfpy.timers
print(f"Timers after GC: {len(timers)}")
for t in timers:
print(f" - {t.name}")
# In headless mode, use step() to advance time and process timers
print("\nAdvancing time with step()...")
for i in range(6):
mcrfpy.step(50) # Advance 50ms per step = 300ms total
print(f" Step {i+1}: fire_count={fire_count}")
# Now check results
print(f"\n=== Final Results ===")
print(f"Fire count: {fire_count}")
# Check that we can still find the timer in mcrfpy.timers
timers = mcrfpy.timers
print(f"Number of timers: {len(timers)}")
orphan_found = False
for t in timers:
print(f" - Timer: name={t.name}, interval={t.interval}")
if t.name == "orphan_timer":
orphan_found = True
# Stop it now that we've verified it exists
t.stop()
print(f" -> Stopped orphan timer")
# Verify the orphan timer was found and fired
if not orphan_found:
print("FAIL: Orphan timer not found in mcrfpy.timers")
sys.exit(1)
if fire_count == 0:
print("FAIL: Orphan timer never fired")
sys.exit(1)
print(f"\nPASS: Orphan timer fired {fire_count} times and was accessible via mcrfpy.timers")
sys.exit(0)

View file

@ -0,0 +1,83 @@
"""Test for issue #180: Stopped timers with user reference should stay alive.
This test verifies that:
1. A stopped timer with a user reference remains accessible
2. A stopped timer can be restarted
3. A stopped timer without references is properly cleaned up
"""
import mcrfpy
import gc
import sys
fire_count = 0
def on_timer(timer, runtime):
"""Timer callback."""
global fire_count
fire_count += 1
print(f"Timer fired! count={fire_count}")
# Set up test scene
scene = mcrfpy.Scene("test")
mcrfpy.current_scene = scene
print("=== Test 1: Stopped timer with reference stays alive ===")
# Create timer and keep reference
my_timer = mcrfpy.Timer("kept_timer", on_timer, 50)
print(f"Created timer: {my_timer.name}, active={my_timer.active}")
# Advance time to fire once
mcrfpy.step(60)
print(f"After step: fire_count={fire_count}")
# Stop the timer
my_timer.stop()
print(f"Stopped timer: active={my_timer.active}, stopped={my_timer.stopped}")
# Timer should NOT be in mcrfpy.timers (it's stopped)
timers = mcrfpy.timers
timer_names = [t.name for t in timers]
print(f"Timers after stop: {timer_names}")
if "kept_timer" in timer_names:
print("Note: Stopped timer still in mcrfpy.timers (expected - it was accessed)")
# But we should still have our reference and can restart
print(f"Our reference still valid: {my_timer.name}")
my_timer.restart()
print(f"Restarted timer: active={my_timer.active}")
# Advance time and verify it fires again
old_count = fire_count
mcrfpy.step(60)
print(f"After restart step: fire_count={fire_count}")
if fire_count <= old_count:
print("FAIL: Restarted timer didn't fire")
sys.exit(1)
print("\n=== Test 2: Stopped timer without reference is cleaned up ===")
# Create another timer, stop it, and lose the reference
temp_timer = mcrfpy.Timer("temp_timer", on_timer, 50)
temp_timer.stop()
print(f"Created and stopped temp_timer")
# Clear reference and GC
del temp_timer
gc.collect()
# The timer should be gone (stopped + no reference = GC'd)
timers = mcrfpy.timers
timer_names = [t.name for t in timers]
print(f"Timers after del+GC: {timer_names}")
# Note: temp_timer might still be there if it was retrieved before - that's OK
# The key test is that it WON'T fire since it's stopped
# Clean up
my_timer.stop()
print("\nPASS: Timer lifecycle works correctly")
sys.exit(0)

View file

@ -0,0 +1,97 @@
#!/usr/bin/env python3
"""Test for issue #182: Caption size, w, and h read-only properties.
This test verifies that:
1. Caption.size returns a Vector with the text dimensions
2. Caption.w and Caption.h return float values matching size.x and size.y
3. All three properties are read-only (setting raises AttributeError)
"""
import mcrfpy
import sys
def test_caption_size_properties():
"""Test Caption size, w, and h properties."""
# Create a caption with some text
caption = mcrfpy.Caption(text="Hello World", pos=(100, 100), font_size=24)
# Test 1: size property exists and returns a Vector
size = caption.size
print(f"caption.size = {size}")
# Verify it's a Vector
assert hasattr(size, 'x') and hasattr(size, 'y'), "size should be a Vector with x and y attributes"
print(f" size.x = {size.x}, size.y = {size.y}")
# Test 2: size values are positive (text has non-zero dimensions)
assert size.x > 0, f"size.x should be positive, got {size.x}"
assert size.y > 0, f"size.y should be positive, got {size.y}"
print(" size values are positive: PASS")
# Test 3: w and h properties exist and return floats
w = caption.w
h = caption.h
print(f"caption.w = {w}, caption.h = {h}")
assert isinstance(w, float), f"w should be a float, got {type(w)}"
assert isinstance(h, float), f"h should be a float, got {type(h)}"
print(" w and h are floats: PASS")
# Test 4: w and h match size.x and size.y
assert abs(w - size.x) < 0.001, f"w ({w}) should match size.x ({size.x})"
assert abs(h - size.y) < 0.001, f"h ({h}) should match size.y ({size.y})"
print(" w/h match size.x/size.y: PASS")
# Test 5: size is read-only
try:
caption.size = mcrfpy.Vector(50, 50)
print(" ERROR: setting size should raise AttributeError")
sys.exit(1)
except AttributeError:
print(" size is read-only: PASS")
# Test 6: w is read-only
try:
caption.w = 100.0
print(" ERROR: setting w should raise AttributeError")
sys.exit(1)
except AttributeError:
print(" w is read-only: PASS")
# Test 7: h is read-only
try:
caption.h = 50.0
print(" ERROR: setting h should raise AttributeError")
sys.exit(1)
except AttributeError:
print(" h is read-only: PASS")
# Test 8: Changing text changes the size
old_w = caption.w
caption.text = "Hello World! This is a much longer text."
new_w = caption.w
print(f"After changing text: old_w = {old_w}, new_w = {new_w}")
assert new_w > old_w, f"Longer text should have larger width: {new_w} > {old_w}"
print(" Changing text updates size: PASS")
# Test 9: Empty caption has zero or near-zero size
empty_caption = mcrfpy.Caption(text="", pos=(0, 0))
print(f"Empty caption: w={empty_caption.w}, h={empty_caption.h}")
# Note: Even empty text might have some height due to font metrics
assert empty_caption.w == 0 or empty_caption.w < 1, f"Empty text should have ~zero width, got {empty_caption.w}"
print(" Empty caption has minimal size: PASS")
print("\nAll tests passed!")
return True
if __name__ == "__main__":
try:
test_caption_size_properties()
print("PASS")
sys.exit(0)
except Exception as e:
print(f"FAIL: {e}")
import traceback
traceback.print_exc()
sys.exit(1)

View file

@ -0,0 +1,95 @@
#!/usr/bin/env python3
"""Test for issues #184 (mcrfpy.window singleton) and #189 (hide non-instantiable classes)"""
import mcrfpy
import sys
errors = []
# Test #184: mcrfpy.window singleton exists
print("Testing #184: mcrfpy.window singleton...")
try:
window = mcrfpy.window
print(f" mcrfpy.window exists: {window}")
except AttributeError as e:
errors.append(f"#184 FAIL: mcrfpy.window not found: {e}")
# Check window has expected attributes
if hasattr(mcrfpy, 'window'):
window = mcrfpy.window
# Check for expected properties
expected_attrs = ['resolution', 'fullscreen', 'vsync', 'title', 'visible']
for attr in expected_attrs:
if hasattr(window, attr):
print(f" window.{attr} = {getattr(window, attr)}")
else:
errors.append(f"#184 FAIL: mcrfpy.window missing attribute '{attr}'")
# Check that Window TYPE still exists (for isinstance checks)
if hasattr(mcrfpy, 'Window'):
print(f" mcrfpy.Window type exists: {mcrfpy.Window}")
# Verify window is an instance of Window
if isinstance(mcrfpy.window, mcrfpy.Window):
print(" isinstance(mcrfpy.window, mcrfpy.Window) = True")
else:
errors.append("#184 FAIL: mcrfpy.window is not an instance of mcrfpy.Window")
else:
errors.append("#184 FAIL: mcrfpy.Window type not found")
# Test #189: Hidden classes should NOT be accessible
print("\nTesting #189: Hidden classes should raise AttributeError...")
hidden_classes = [
'EntityCollection',
'UICollection',
'UICollectionIter',
'UIEntityCollectionIter',
'GridPoint',
'GridPointState'
]
for class_name in hidden_classes:
try:
cls = getattr(mcrfpy, class_name)
errors.append(f"#189 FAIL: mcrfpy.{class_name} should be hidden but is accessible: {cls}")
except AttributeError:
print(f" mcrfpy.{class_name} correctly raises AttributeError")
# Test that hidden classes still WORK (just not exported)
print("\nTesting that internal types still function correctly...")
# Create a scene to test UICollection
scene = mcrfpy.Scene("test_scene")
scene.activate()
# Test UICollection via .children
print(" Getting scene.children...")
children = scene.children
print(f" scene.children works: {children}")
children_type = type(children)
print(f" type(scene.children) = {children_type}")
if 'UICollection' in str(children_type):
print(" UICollection type works correctly (internal use)")
else:
errors.append(f"#189 FAIL: scene.children returned unexpected type: {children_type}")
# Test that Drawable IS still exported (should NOT be hidden)
print("\nTesting that Drawable is still exported...")
if hasattr(mcrfpy, 'Drawable'):
print(f" mcrfpy.Drawable exists: {mcrfpy.Drawable}")
else:
errors.append("FAIL: mcrfpy.Drawable should still be exported but is missing")
# Summary
print("\n" + "="*60)
if errors:
print("FAILURES:")
for e in errors:
print(f" {e}")
print(f"\nFAIL: {len(errors)} error(s)")
sys.exit(1)
else:
print("PASS: All tests passed for issues #184 and #189")
sys.exit(0)

View file

@ -0,0 +1,122 @@
"""Test for issues #185 and #188: bounds handling changes.
Issue #185: Remove .get_bounds() method - redundant with .bounds property
Issue #188: Change .bounds and .global_bounds to return (pos, size) as pair of Vectors
"""
import mcrfpy
import sys
def test_bounds():
"""Test that bounds returns (Vector, Vector) tuple."""
print("Testing bounds format...")
# Create a Frame with known position and size
frame = mcrfpy.Frame(pos=(100, 200), size=(300, 400))
bounds = frame.bounds
# Should be a tuple of 2 elements
assert isinstance(bounds, tuple), f"Expected tuple, got {type(bounds)}"
assert len(bounds) == 2, f"Expected 2 elements, got {len(bounds)}"
pos, size = bounds
# Check that pos is a Vector with correct values
assert isinstance(pos, mcrfpy.Vector), f"Expected Vector for pos, got {type(pos)}"
assert pos.x == 100, f"Expected pos.x=100, got {pos.x}"
assert pos.y == 200, f"Expected pos.y=200, got {pos.y}"
# Check that size is a Vector with correct values
assert isinstance(size, mcrfpy.Vector), f"Expected Vector for size, got {type(size)}"
assert size.x == 300, f"Expected size.x=300, got {size.x}"
assert size.y == 400, f"Expected size.y=400, got {size.y}"
print(" Frame bounds: PASS")
def test_global_bounds():
"""Test that global_bounds returns (Vector, Vector) tuple."""
print("Testing global_bounds format...")
frame = mcrfpy.Frame(pos=(50, 75), size=(150, 250))
global_bounds = frame.global_bounds
# Should be a tuple of 2 elements
assert isinstance(global_bounds, tuple), f"Expected tuple, got {type(global_bounds)}"
assert len(global_bounds) == 2, f"Expected 2 elements, got {len(global_bounds)}"
pos, size = global_bounds
assert isinstance(pos, mcrfpy.Vector), f"Expected Vector for pos, got {type(pos)}"
assert isinstance(size, mcrfpy.Vector), f"Expected Vector for size, got {type(size)}"
print(" Frame global_bounds: PASS")
def test_get_bounds_removed():
"""Test that get_bounds() method has been removed."""
print("Testing get_bounds removal...")
frame = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
assert not hasattr(frame, 'get_bounds'), "get_bounds method should be removed"
print(" get_bounds removed: PASS")
def test_caption_bounds():
"""Test bounds on Caption."""
print("Testing Caption bounds...")
caption = mcrfpy.Caption(text="Test", pos=(25, 50))
bounds = caption.bounds
assert isinstance(bounds, tuple), f"Expected tuple, got {type(bounds)}"
assert len(bounds) == 2, f"Expected 2 elements, got {len(bounds)}"
pos, size = bounds
assert isinstance(pos, mcrfpy.Vector), f"Expected Vector for pos, got {type(pos)}"
assert isinstance(size, mcrfpy.Vector), f"Expected Vector for size, got {type(size)}"
# Caption should not have get_bounds
assert not hasattr(caption, 'get_bounds'), "get_bounds method should be removed from Caption"
print(" Caption bounds: PASS")
def test_sprite_bounds():
"""Test bounds on Sprite."""
print("Testing Sprite bounds...")
sprite = mcrfpy.Sprite(pos=(10, 20))
bounds = sprite.bounds
assert isinstance(bounds, tuple), f"Expected tuple, got {type(bounds)}"
assert len(bounds) == 2, f"Expected 2 elements, got {len(bounds)}"
pos, size = bounds
assert isinstance(pos, mcrfpy.Vector), f"Expected Vector for pos, got {type(pos)}"
assert isinstance(size, mcrfpy.Vector), f"Expected Vector for size, got {type(size)}"
# Sprite should not have get_bounds
assert not hasattr(sprite, 'get_bounds'), "get_bounds method should be removed from Sprite"
print(" Sprite bounds: PASS")
# Run tests
print("=" * 60)
print("Testing Issues #185 and #188: Bounds Handling")
print("=" * 60)
try:
test_bounds()
test_global_bounds()
test_get_bounds_removed()
test_caption_bounds()
test_sprite_bounds()
print("=" * 60)
print("All tests PASSED!")
print("=" * 60)
sys.exit(0)
except AssertionError as e:
print(f"FAILED: {e}")
sys.exit(1)
except Exception as e:
print(f"ERROR: {e}")
import traceback
traceback.print_exc()
sys.exit(1)

View file

@ -0,0 +1,95 @@
#!/usr/bin/env python3
"""Test for issue #190: Expanded TileLayer and ColorLayer __init__ documentation.
This test verifies that the documentation for ColorLayer and TileLayer
contains the expected key phrases and is comprehensive.
"""
import mcrfpy
import sys
def test_colorlayer_docs():
"""Test ColorLayer documentation completeness."""
doc = mcrfpy.ColorLayer.__doc__
if not doc:
print("FAIL: ColorLayer.__doc__ is empty or None")
return False
print("=== ColorLayer Documentation ===")
print(doc)
print()
# Check for key phrases
required_phrases = [
"grid_size",
"z_index",
"RGBA",
"at(x, y)",
"set(x, y",
"fill(",
"Grid.add_layer",
"visible",
"Example",
]
missing = []
for phrase in required_phrases:
if phrase not in doc:
missing.append(phrase)
if missing:
print(f"FAIL: ColorLayer docs missing phrases: {missing}")
return False
print("ColorLayer documentation: PASS")
return True
def test_tilelayer_docs():
"""Test TileLayer documentation completeness."""
doc = mcrfpy.TileLayer.__doc__
if not doc:
print("FAIL: TileLayer.__doc__ is empty or None")
return False
print("=== TileLayer Documentation ===")
print(doc)
print()
# Check for key phrases
required_phrases = [
"grid_size",
"z_index",
"texture",
"at(x, y)",
"set(x, y",
"fill(",
"-1", # Special value for no tile
"sprite",
"Grid.add_layer",
"visible",
"Example",
]
missing = []
for phrase in required_phrases:
if phrase not in doc:
missing.append(phrase)
if missing:
print(f"FAIL: TileLayer docs missing phrases: {missing}")
return False
print("TileLayer documentation: PASS")
return True
# Run tests
colorlayer_ok = test_colorlayer_docs()
tilelayer_ok = test_tilelayer_docs()
if colorlayer_ok and tilelayer_ok:
print("\nAll documentation tests PASSED")
sys.exit(0)
else:
print("\nDocumentation tests FAILED")
sys.exit(1)