275 lines
9.2 KiB
Python
275 lines
9.2 KiB
Python
|
|
"""
|
||
|
|
Unit tests for the modernized Grid Layer API.
|
||
|
|
|
||
|
|
Tests:
|
||
|
|
1. Layer name in constructor
|
||
|
|
2. Lazy allocation (size (0,0) auto-resizes)
|
||
|
|
3. layer.grid property (getter and setter)
|
||
|
|
4. grid.layer(name) lookup
|
||
|
|
5. grid.layers returns tuple
|
||
|
|
6. add_layer accepts layer objects
|
||
|
|
7. remove_layer by name and by layer object
|
||
|
|
8. Name collision handling
|
||
|
|
"""
|
||
|
|
|
||
|
|
import mcrfpy
|
||
|
|
import sys
|
||
|
|
|
||
|
|
def test_layer_name_in_constructor():
|
||
|
|
"""Test that layers can be created with names."""
|
||
|
|
print("Test 1: Layer name in constructor...")
|
||
|
|
|
||
|
|
color_layer = mcrfpy.ColorLayer(name='fog', z_index=-1)
|
||
|
|
assert color_layer.name == 'fog', f"Expected 'fog', got {color_layer.name}"
|
||
|
|
|
||
|
|
tile_layer = mcrfpy.TileLayer(name='terrain', z_index=-2)
|
||
|
|
assert tile_layer.name == 'terrain', f"Expected 'terrain', got {tile_layer.name}"
|
||
|
|
|
||
|
|
# Name should appear in repr
|
||
|
|
assert 'fog' in repr(color_layer), f"Name not in repr: {repr(color_layer)}"
|
||
|
|
assert 'terrain' in repr(tile_layer), f"Name not in repr: {repr(tile_layer)}"
|
||
|
|
|
||
|
|
# Layers without name should work too
|
||
|
|
unnamed = mcrfpy.ColorLayer(z_index=0)
|
||
|
|
assert unnamed.name == '', f"Expected empty string, got {unnamed.name}"
|
||
|
|
|
||
|
|
print(" PASS")
|
||
|
|
|
||
|
|
def test_lazy_allocation():
|
||
|
|
"""Test that layers with size (0,0) auto-resize when attached to grid."""
|
||
|
|
print("Test 2: Lazy allocation...")
|
||
|
|
|
||
|
|
layer = mcrfpy.ColorLayer(name='test')
|
||
|
|
assert layer.grid_size == (0, 0), f"Expected (0,0), got {layer.grid_size}"
|
||
|
|
|
||
|
|
grid = mcrfpy.Grid(grid_size=(10, 8), layers=[layer])
|
||
|
|
assert layer.grid_size == (10, 8), f"Expected (10,8), got {layer.grid_size}"
|
||
|
|
|
||
|
|
print(" PASS")
|
||
|
|
|
||
|
|
def test_layer_grid_property():
|
||
|
|
"""Test that layer.grid property works for getting and setting."""
|
||
|
|
print("Test 3: layer.grid property...")
|
||
|
|
|
||
|
|
layer = mcrfpy.ColorLayer(name='overlay', z_index=0)
|
||
|
|
assert layer.grid is None, f"Expected None, got {layer.grid}"
|
||
|
|
|
||
|
|
grid = mcrfpy.Grid(grid_size=(5, 5), layers=[])
|
||
|
|
layer.grid = grid
|
||
|
|
|
||
|
|
assert layer.grid is not None, "layer.grid should not be None after assignment"
|
||
|
|
assert len(grid.layers) == 1, f"Expected 1 layer, got {len(grid.layers)}"
|
||
|
|
assert layer.grid_size == (5, 5), f"Expected (5,5), got {layer.grid_size}"
|
||
|
|
|
||
|
|
# Unlink by setting to None
|
||
|
|
layer.grid = None
|
||
|
|
assert layer.grid is None, "layer.grid should be None after unsetting"
|
||
|
|
assert len(grid.layers) == 0, f"Expected 0 layers, got {len(grid.layers)}"
|
||
|
|
|
||
|
|
print(" PASS")
|
||
|
|
|
||
|
|
def test_grid_layer_name_lookup():
|
||
|
|
"""Test that grid.layer(name) finds layers by name."""
|
||
|
|
print("Test 4: grid.layer(name) lookup...")
|
||
|
|
|
||
|
|
fog = mcrfpy.ColorLayer(name='fog', z_index=-1)
|
||
|
|
terrain = mcrfpy.TileLayer(name='terrain', z_index=-2)
|
||
|
|
grid = mcrfpy.Grid(grid_size=(5, 5), layers=[fog, terrain])
|
||
|
|
|
||
|
|
retrieved_fog = grid.layer('fog')
|
||
|
|
assert retrieved_fog is not None, "Should find 'fog' layer"
|
||
|
|
assert retrieved_fog.name == 'fog', f"Expected 'fog', got {retrieved_fog.name}"
|
||
|
|
|
||
|
|
retrieved_terrain = grid.layer('terrain')
|
||
|
|
assert retrieved_terrain is not None, "Should find 'terrain' layer"
|
||
|
|
assert retrieved_terrain.name == 'terrain', f"Expected 'terrain', got {retrieved_terrain.name}"
|
||
|
|
|
||
|
|
# Non-existent name should return None
|
||
|
|
result = grid.layer('nonexistent')
|
||
|
|
assert result is None, f"Expected None for nonexistent, got {result}"
|
||
|
|
|
||
|
|
print(" PASS")
|
||
|
|
|
||
|
|
def test_layers_returns_tuple():
|
||
|
|
"""Test that grid.layers returns a tuple (immutable)."""
|
||
|
|
print("Test 5: grid.layers returns tuple...")
|
||
|
|
|
||
|
|
fog = mcrfpy.ColorLayer(name='fog')
|
||
|
|
grid = mcrfpy.Grid(grid_size=(5, 5), layers=[fog])
|
||
|
|
|
||
|
|
layers = grid.layers
|
||
|
|
assert isinstance(layers, tuple), f"Expected tuple, got {type(layers)}"
|
||
|
|
|
||
|
|
print(" PASS")
|
||
|
|
|
||
|
|
def test_add_layer_accepts_objects():
|
||
|
|
"""Test that add_layer accepts ColorLayer and TileLayer objects."""
|
||
|
|
print("Test 6: add_layer accepts objects...")
|
||
|
|
|
||
|
|
grid = mcrfpy.Grid(grid_size=(5, 5), layers=[])
|
||
|
|
assert len(grid.layers) == 0, f"Expected 0 layers, got {len(grid.layers)}"
|
||
|
|
|
||
|
|
new_layer = mcrfpy.TileLayer(name='overlay', z_index=1)
|
||
|
|
returned = grid.add_layer(new_layer)
|
||
|
|
|
||
|
|
assert len(grid.layers) == 1, f"Expected 1 layer, got {len(grid.layers)}"
|
||
|
|
assert new_layer.grid is not None, "Layer should be attached to grid"
|
||
|
|
assert returned is new_layer, "Should return the same layer object"
|
||
|
|
|
||
|
|
# Add another layer
|
||
|
|
color = mcrfpy.ColorLayer(name='highlights', z_index=2)
|
||
|
|
grid.add_layer(color)
|
||
|
|
assert len(grid.layers) == 2, f"Expected 2 layers, got {len(grid.layers)}"
|
||
|
|
|
||
|
|
print(" PASS")
|
||
|
|
|
||
|
|
def test_remove_layer_by_name():
|
||
|
|
"""Test that remove_layer accepts layer name as string."""
|
||
|
|
print("Test 7a: remove_layer by name...")
|
||
|
|
|
||
|
|
fog = mcrfpy.ColorLayer(name='fog')
|
||
|
|
terrain = mcrfpy.TileLayer(name='terrain')
|
||
|
|
grid = mcrfpy.Grid(grid_size=(5, 5), layers=[fog, terrain])
|
||
|
|
|
||
|
|
assert len(grid.layers) == 2
|
||
|
|
|
||
|
|
grid.remove_layer('fog')
|
||
|
|
assert len(grid.layers) == 1, f"Expected 1 layer, got {len(grid.layers)}"
|
||
|
|
assert grid.layer('fog') is None, "fog should be removed"
|
||
|
|
assert grid.layer('terrain') is not None, "terrain should remain"
|
||
|
|
|
||
|
|
# Removing non-existent should raise KeyError
|
||
|
|
try:
|
||
|
|
grid.remove_layer('nonexistent')
|
||
|
|
assert False, "Should raise KeyError for nonexistent layer"
|
||
|
|
except KeyError:
|
||
|
|
pass # Expected
|
||
|
|
|
||
|
|
print(" PASS")
|
||
|
|
|
||
|
|
def test_remove_layer_by_object():
|
||
|
|
"""Test that remove_layer accepts layer object."""
|
||
|
|
print("Test 7b: remove_layer by object...")
|
||
|
|
|
||
|
|
fog = mcrfpy.ColorLayer(name='fog')
|
||
|
|
terrain = mcrfpy.TileLayer(name='terrain')
|
||
|
|
grid = mcrfpy.Grid(grid_size=(5, 5), layers=[fog, terrain])
|
||
|
|
|
||
|
|
grid.remove_layer(terrain)
|
||
|
|
assert len(grid.layers) == 1, f"Expected 1 layer, got {len(grid.layers)}"
|
||
|
|
assert terrain.grid is None, "Removed layer should have grid=None"
|
||
|
|
|
||
|
|
print(" PASS")
|
||
|
|
|
||
|
|
def test_name_collision_replaces():
|
||
|
|
"""Test that adding a layer with existing name replaces the old layer."""
|
||
|
|
print("Test 8: Name collision replaces old layer...")
|
||
|
|
|
||
|
|
old = mcrfpy.ColorLayer(name='fog')
|
||
|
|
grid = mcrfpy.Grid(grid_size=(5, 5), layers=[old])
|
||
|
|
|
||
|
|
assert len(grid.layers) == 1
|
||
|
|
assert old.grid is not None
|
||
|
|
|
||
|
|
# Add new layer with same name
|
||
|
|
new = mcrfpy.ColorLayer(name='fog')
|
||
|
|
grid.add_layer(new)
|
||
|
|
|
||
|
|
assert len(grid.layers) == 1, f"Expected 1 layer after replacement, got {len(grid.layers)}"
|
||
|
|
assert old.grid is None, "Old layer should be unlinked"
|
||
|
|
assert new.grid is not None, "New layer should be linked"
|
||
|
|
|
||
|
|
# Verify it's the new one
|
||
|
|
retrieved = grid.layer('fog')
|
||
|
|
# Both have same name, but we can check the new one is attached
|
||
|
|
assert retrieved is not None
|
||
|
|
|
||
|
|
print(" PASS")
|
||
|
|
|
||
|
|
def test_size_validation():
|
||
|
|
"""Test that mismatched sizes raise errors."""
|
||
|
|
print("Test 9: Size validation...")
|
||
|
|
|
||
|
|
# Layer with specific size that doesn't match grid
|
||
|
|
layer = mcrfpy.ColorLayer(name='test', grid_size=(10, 10))
|
||
|
|
grid = mcrfpy.Grid(grid_size=(5, 5), layers=[])
|
||
|
|
|
||
|
|
try:
|
||
|
|
grid.add_layer(layer)
|
||
|
|
assert False, "Should raise ValueError for size mismatch"
|
||
|
|
except ValueError as e:
|
||
|
|
assert "size" in str(e).lower(), f"Error should mention size: {e}"
|
||
|
|
|
||
|
|
print(" PASS")
|
||
|
|
|
||
|
|
def test_protected_names():
|
||
|
|
"""Test that protected names (walkable, transparent) are rejected."""
|
||
|
|
print("Test 10: Protected names...")
|
||
|
|
|
||
|
|
layer = mcrfpy.ColorLayer(name='walkable')
|
||
|
|
grid = mcrfpy.Grid(grid_size=(5, 5), layers=[])
|
||
|
|
|
||
|
|
try:
|
||
|
|
grid.add_layer(layer)
|
||
|
|
assert False, "Should raise ValueError for protected name 'walkable'"
|
||
|
|
except ValueError as e:
|
||
|
|
assert "reserved" in str(e).lower() or "walkable" in str(e).lower(), f"Error: {e}"
|
||
|
|
|
||
|
|
layer2 = mcrfpy.ColorLayer(name='transparent')
|
||
|
|
try:
|
||
|
|
grid.add_layer(layer2)
|
||
|
|
assert False, "Should raise ValueError for protected name 'transparent'"
|
||
|
|
except ValueError as e:
|
||
|
|
assert "reserved" in str(e).lower() or "transparent" in str(e).lower(), f"Error: {e}"
|
||
|
|
|
||
|
|
print(" PASS")
|
||
|
|
|
||
|
|
def test_already_attached_error():
|
||
|
|
"""Test that attaching a layer to two grids raises error."""
|
||
|
|
print("Test 11: Already attached error...")
|
||
|
|
|
||
|
|
layer = mcrfpy.ColorLayer(name='shared')
|
||
|
|
grid1 = mcrfpy.Grid(grid_size=(5, 5), layers=[layer])
|
||
|
|
grid2 = mcrfpy.Grid(grid_size=(5, 5), layers=[])
|
||
|
|
|
||
|
|
try:
|
||
|
|
grid2.add_layer(layer)
|
||
|
|
assert False, "Should raise ValueError for already attached layer"
|
||
|
|
except ValueError as e:
|
||
|
|
assert "attached" in str(e).lower() or "another" in str(e).lower(), f"Error: {e}"
|
||
|
|
|
||
|
|
print(" PASS")
|
||
|
|
|
||
|
|
def run_all_tests():
|
||
|
|
"""Run all tests."""
|
||
|
|
print("Running Grid Layer API tests...\n")
|
||
|
|
|
||
|
|
test_layer_name_in_constructor()
|
||
|
|
test_lazy_allocation()
|
||
|
|
test_layer_grid_property()
|
||
|
|
test_grid_layer_name_lookup()
|
||
|
|
test_layers_returns_tuple()
|
||
|
|
test_add_layer_accepts_objects()
|
||
|
|
test_remove_layer_by_name()
|
||
|
|
test_remove_layer_by_object()
|
||
|
|
test_name_collision_replaces()
|
||
|
|
test_size_validation()
|
||
|
|
test_protected_names()
|
||
|
|
test_already_attached_error()
|
||
|
|
|
||
|
|
print("\n" + "="*50)
|
||
|
|
print("All tests PASSED!")
|
||
|
|
print("="*50)
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
try:
|
||
|
|
run_all_tests()
|
||
|
|
sys.exit(0)
|
||
|
|
except AssertionError as e:
|
||
|
|
print(f"\nFAIL: {e}")
|
||
|
|
sys.exit(1)
|
||
|
|
except Exception as e:
|
||
|
|
print(f"\nERROR: {type(e).__name__}: {e}")
|
||
|
|
import traceback
|
||
|
|
traceback.print_exc()
|
||
|
|
sys.exit(1)
|