Skip to content

Commit 626c103

Browse files
committed
add comprehensive test coverage for ProceduralData, PyscrollDataAdapter, and TiledMapData
1 parent 85c2aca commit 626c103

3 files changed

Lines changed: 259 additions & 3 deletions

File tree

‎tests/test_procedural_data.py‎

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,118 @@ def test_get_animations(proc):
6262
gid, frames = animations[0]
6363
assert gid == proc._GID_WATER
6464
assert all(isinstance(f[0], int) and isinstance(f[1], int) for f in frames)
65+
66+
67+
def test_reload_animations_populates_structures(proc):
68+
proc.reload_animations()
69+
assert proc._tracked_gids == {proc._GID_WATER}
70+
assert len(proc._animation_map) == 1
71+
assert len(proc._animation_queue) == 1
72+
73+
74+
def test_process_animation_queue_changes_frames(proc):
75+
proc.reload_animations()
76+
77+
# Touch a WATER tile so it becomes tracked for animation
78+
# (1,0) is WATER because 1+0 = 1 (odd)
79+
proc.get_tile_image(1, 0, 0)
80+
81+
# Get the real animation token
82+
token = next(iter(proc._animation_queue))
83+
84+
# Force the animation to trigger immediately
85+
token.next = 0
86+
87+
tile_view = pygame.Rect(0, 0, 40, 30)
88+
updates = proc.process_animation_queue(tile_view)
89+
90+
# Should produce at least one updated tile
91+
assert updates
92+
93+
x, y, layer, surf = updates[0]
94+
95+
# The returned surface must be one of the animation frames
96+
expected_frames = {frame.image for frame in token.frames}
97+
98+
assert surf in expected_frames
99+
100+
101+
def test_get_tile_image_animated(proc):
102+
proc.reload_animations()
103+
# Force animation tracking
104+
proc._get_tile_gid = lambda x, y, l: proc._GID_WATER
105+
106+
img = proc.get_tile_image(0, 0, 0)
107+
assert isinstance(img, Surface)
108+
109+
110+
def test_surfaces_are_unique(proc):
111+
assert proc._surfaces[proc._GID_GRASS] is not proc._surfaces[proc._GID_WATER]
112+
113+
114+
def test_convert_surfaces_noop(proc):
115+
surf = Surface((32, 32))
116+
proc.convert_surfaces(surf, alpha=True) # should not crash
117+
118+
119+
def test_process_animation_queue_second_cycle(proc):
120+
proc.reload_animations()
121+
122+
# Touch a WATER tile so it becomes tracked for animation
123+
proc.get_tile_image(1, 0, 0) # (1,0) is WATER
124+
125+
# Get the real animation token
126+
token = next(iter(proc._animation_queue))
127+
128+
# Force animation to trigger immediately
129+
token.next = 0
130+
131+
tile_view = pygame.Rect(0, 0, 40, 30)
132+
133+
# First update
134+
updates1 = proc.process_animation_queue(tile_view)
135+
assert updates1
136+
_, _, _, surf1 = updates1[0]
137+
138+
# Force next frame to be ready
139+
token.next = 0
140+
141+
# Second update
142+
updates2 = proc.process_animation_queue(tile_view)
143+
assert updates2
144+
_, _, _, surf2 = updates2[0]
145+
146+
# Both frames must be valid animation frames
147+
expected_frames = {frame.image for frame in token.frames}
148+
assert surf1 in expected_frames
149+
assert surf2 in expected_frames
150+
151+
152+
def test_process_animation_queue_stress(proc):
153+
proc.reload_animations()
154+
155+
# Touch a WATER tile so it becomes tracked for animation
156+
# (1,0) is WATER because 1+0 = 1 (odd)
157+
proc.get_tile_image(1, 0, 0)
158+
159+
token = next(iter(proc._animation_queue))
160+
tile_view = pygame.Rect(0, 0, 40, 30)
161+
162+
# Collect all possible animation frame surfaces
163+
expected_frames = {frame.image for frame in token.frames}
164+
165+
# Run many cycles to ensure stability
166+
for _ in range(50):
167+
# Force animation to be ready
168+
token.next = 0
169+
170+
updates = proc.process_animation_queue(tile_view)
171+
assert updates, "Animation stopped producing updates during stress test"
172+
173+
x, y, layer, surf = updates[0]
174+
175+
# Returned frame must always be valid
176+
assert surf in expected_frames
177+
178+
# Position must remain tracked
179+
assert (1, 0, 0) in token.positions

‎tests/test_pyscroll_data_adapter.py‎

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from collections.abc import Iterable
2+
from unittest.mock import MagicMock
23

34
import pytest
45
from pygame.rect import Rect
@@ -129,3 +130,75 @@ def test_is_on_map(adapter, x, y, expected):
129130
)
130131
def test_pixel_to_tile(adapter, px, py, expected):
131132
assert adapter.pixel_to_tile(px, py) == expected
133+
134+
135+
def test_get_tile_image_animated_tile(adapter):
136+
surf = Surface((32, 32))
137+
adapter._animated_tile[(1, 2, 0)] = surf
138+
assert adapter.get_tile_image(1, 2, 0) is surf
139+
140+
141+
def test_get_tile_image_animation_map_initial_frame(adapter):
142+
surf = Surface((32, 32))
143+
token = MagicMock()
144+
token.frames = [MagicMock(image=surf)]
145+
token.positions = set()
146+
adapter._animation_map = {123: token}
147+
adapter._get_tile_gid = lambda x, y, l: 123
148+
149+
img = adapter.get_tile_image(0, 0, 0)
150+
assert img is surf
151+
assert (0, 0, 0) in token.positions
152+
153+
154+
def test_process_animation_queue_updates_tiles(adapter):
155+
surf1 = Surface((32, 32))
156+
surf2 = Surface((32, 32))
157+
158+
token = MagicMock()
159+
token.next = 0
160+
token.positions = {(1, 1, 0)}
161+
162+
# After first advance, next should be > last_time to break the loop
163+
def advance_mock(t):
164+
token.next = t + 999 # ensure loop stops
165+
return MagicMock(image=surf2)
166+
167+
token.advance.side_effect = advance_mock
168+
169+
adapter._animation_queue = [token]
170+
adapter._last_time = 1
171+
adapter._get_tile_image = lambda x, y, l: surf1
172+
173+
tile_view = Rect(0, 0, 10, 10)
174+
updates = adapter.process_animation_queue(tile_view)
175+
176+
assert updates == [(1, 1, 0, surf2)]
177+
178+
179+
def test_get_tile_images_by_rect_iteration_order(adapter):
180+
calls = []
181+
adapter._get_tile_image = lambda x, y, l: calls.append((x, y)) or None
182+
183+
rect = Rect(0, 0, 1, 1)
184+
list(adapter.get_tile_images_by_rect(rect))
185+
186+
assert calls == [(0, 0)]
187+
188+
189+
def test_update_time_does_not_advance_when_paused(adapter):
190+
adapter.pause_animations()
191+
before = adapter._last_time
192+
adapter._update_time()
193+
assert adapter._last_time == before
194+
195+
196+
def test_set_animation_speed_multiplier_applies(adapter):
197+
token1 = MagicMock()
198+
token2 = MagicMock()
199+
adapter._animation_queue = [token1, token2]
200+
201+
adapter.set_animation_speed_multiplier(2.0)
202+
203+
assert token1.speed_multiplier == 2.0
204+
assert token2.speed_multiplier == 2.0

‎tests/test_tiled_map_data.py‎

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,18 @@ def mock_tmx():
1616
mock.height = 10
1717
mock.visible_tile_layers = [0]
1818

19-
mock.images = [Surface((16, 16)), Surface((16, 16))]
19+
img0 = Surface((16, 16))
20+
img1 = Surface((16, 16))
21+
img0.fill((255, 0, 0))
22+
img1.fill((0, 255, 0))
23+
mock.images = [img0, img1]
2024

2125
mock.layers = [MagicMock()]
26+
mock.layers[0].visible = True
2227
mock.layers[0].data = [[1 for _ in range(10)] for _ in range(10)]
2328

24-
mock.tile_properties = {1: {"frames": [(0, 100)]}}
25-
mock.get_tile_image.return_value = Surface((16, 16))
29+
mock.tile_properties = {1: {"frames": [(0, 100), (1, 100)]}}
30+
mock.get_tile_image = lambda x, y, l: None
2631
mock.filename = "test.tmx"
2732
return mock
2833

@@ -83,3 +88,66 @@ def test_convert_surfaces_alpha_and_non_alpha(mock_tmx):
8388
assert isinstance(data.tmx.images[0], Surface) or data.tmx.images[0] is None
8489
data.convert_surfaces(parent, alpha=True)
8590
assert isinstance(data.tmx.images[0], Surface) or data.tmx.images[0] is None
91+
92+
93+
def test_reload_animations_populates_structures(tiled_map_data):
94+
tiled_map_data.reload_animations()
95+
assert tiled_map_data._tracked_gids == {1}
96+
assert len(tiled_map_data._animation_map) == 1
97+
assert len(tiled_map_data._animation_queue) >= 1
98+
99+
100+
def test_process_animation_queue_updates_tiles(tiled_map_data):
101+
tiled_map_data.reload_animations()
102+
103+
# Touch tile (0,0,0) so it becomes tracked
104+
tiled_map_data.get_tile_image(0, 0, 0)
105+
target_pos = (0, 0, 0)
106+
107+
# Ensure it is tracked by at least one token
108+
token = None
109+
for t in tiled_map_data._animation_queue:
110+
if target_pos in t.positions:
111+
token = t
112+
break
113+
114+
assert token is not None
115+
116+
# Force this token to be "ready" and ensure call does not crash
117+
token.next = 0
118+
tile_view = pygame.Rect(0, 0, 10, 10)
119+
updates = tiled_map_data.process_animation_queue(tile_view)
120+
121+
assert isinstance(updates, list)
122+
123+
124+
def test_get_tile_images_by_rect_coordinates(tiled_map_data):
125+
rect = (0, 0, 2, 2)
126+
tiles = list(tiled_map_data.get_tile_images_by_rect(rect))
127+
128+
xs = {x for x, y, layer, surf in tiles}
129+
ys = {y for x, y, layer, surf in tiles}
130+
131+
assert xs == {0, 1}
132+
assert ys == {0, 1}
133+
134+
135+
def test_convert_surfaces_modifies_images(mock_tmx):
136+
data = TiledMapData(mock_tmx)
137+
parent = Surface((16, 16))
138+
139+
before = data.tmx.images[0]
140+
data.convert_surfaces(parent, alpha=False)
141+
after = data.tmx.images[0]
142+
143+
assert isinstance(after, Surface)
144+
assert after is not before
145+
146+
147+
def test_get_tile_gid_invalid_layer(tiled_map_data):
148+
assert tiled_map_data._get_tile_gid(0, 0, 99) is None
149+
150+
151+
def test_get_tile_image_invalid_coords(tiled_map_data):
152+
img = tiled_map_data.get_tile_image(999, 999, 0)
153+
assert img is None

0 commit comments

Comments
 (0)