diff --git a/tests/KNOWN_ISSUES.md b/tests/KNOWN_ISSUES.md index 8e4a53c..9fab74a 100644 --- a/tests/KNOWN_ISSUES.md +++ b/tests/KNOWN_ISSUES.md @@ -89,30 +89,28 @@ The following tests have been converted to use `mcrfpy.step()`: - test_animation_removal.py - test_timer_callback.py - test_timer_once.py +- test_simple_callback.py +- test_empty_animation_manager.py +- test_frame_clipping.py +- test_frame_clipping_advanced.py +- test_grid_children.py +- test_color_helpers.py +- test_no_arg_constructors.py +- test_properties_quick.py +- test_simple_drawable.py +- test_python_object_cache.py +- WORKING_automation_test_example.py ## Remaining Timeout Failures These tests still use Timer-based async patterns: -- WORKING_automation_test_example.py - benchmark_logging_test.py - keypress_scene_validation_test.py -- test_empty_animation_manager.py -- test_simple_callback.py **Headless mode tests:** - test_headless_detection.py - test_headless_modes.py -**Other timing-dependent:** -- test_color_helpers.py -- test_frame_clipping.py -- test_frame_clipping_advanced.py -- test_grid_children.py -- test_no_arg_constructors.py -- test_properties_quick.py -- test_python_object_cache.py -- test_simple_drawable.py - ## Running Tests ```bash diff --git a/tests/unit/WORKING_automation_test_example.py b/tests/unit/WORKING_automation_test_example.py index bfa8b82..e7307d2 100644 --- a/tests/unit/WORKING_automation_test_example.py +++ b/tests/unit/WORKING_automation_test_example.py @@ -1,47 +1,11 @@ #!/usr/bin/env python3 -"""Example of CORRECT test pattern using timer callbacks for automation""" +"""Example of CORRECT test pattern using mcrfpy.step() for automation +Refactored from timer-based approach to synchronous step() pattern. +""" import mcrfpy from mcrfpy import automation from datetime import datetime - -def run_automation_tests(timer, runtime): - """This runs AFTER the game loop has started and rendered frames""" - print("\n=== Automation Test Running (1 second after start) ===") - - # NOW we can take screenshots that will show content! - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - filename = f"WORKING_screenshot_{timestamp}.png" - - # Take screenshot - this should now show our red frame - result = automation.screenshot(filename) - print(f"Screenshot taken: {filename} - Result: {result}") - - # Test clicking on the frame - automation.click(200, 200) # Click in center of red frame - - # Test keyboard input - automation.typewrite("Hello from timer callback!") - - # Take another screenshot to show any changes - filename2 = f"WORKING_screenshot_after_click_{timestamp}.png" - automation.screenshot(filename2) - print(f"Second screenshot: {filename2}") - - print("Test completed successfully!") - print("\nThis works because:") - print("1. The game loop has been running for 1 second") - print("2. The scene has been rendered multiple times") - print("3. The RenderTexture now contains actual rendered content") - - # Cancel this timer so it doesn't repeat - timer.stop() - - # Optional: exit after a moment - def exit_game(t, r): - print("Exiting...") - mcrfpy.exit() - global exit_timer - exit_timer = mcrfpy.Timer("exit", exit_game, 500, once=True) +import sys # This code runs during --exec script execution print("=== Setting Up Test Scene ===") @@ -49,6 +13,8 @@ print("=== Setting Up Test Scene ===") # Create scene with visible content timer_test_scene = mcrfpy.Scene("timer_test_scene") timer_test_scene.activate() +mcrfpy.step(0.01) # Initialize scene + ui = timer_test_scene.children # Add a bright red frame that should be visible @@ -60,23 +26,57 @@ ui.append(frame) # Add text caption = mcrfpy.Caption(pos=(150, 150), - text="TIMER TEST - SHOULD BE VISIBLE", + text="STEP TEST - SHOULD BE VISIBLE", fill_color=mcrfpy.Color(255, 255, 255)) caption.font_size = 24 frame.children.append(caption) # Add click handler to demonstrate interaction +click_received = False def frame_clicked(x, y, button): + global click_received + click_received = True print(f"Frame clicked at ({x}, {y}) with button {button}") frame.on_click = frame_clicked -print("Scene setup complete. Setting timer for automation tests...") +print("Scene setup complete.") -# THIS IS THE KEY: Set timer to run AFTER the game loop starts -automation_test_timer = mcrfpy.Timer("automation_test", run_automation_tests, 1000, once=True) +# Step to render the scene +mcrfpy.step(0.1) -print("Timer set. Game loop will start after this script completes.") -print("Automation tests will run 1 second later when content is visible.") +print("\n=== Automation Test Running ===") -# Script ends here - game loop starts next \ No newline at end of file +# NOW we can take screenshots that will show content! +timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") +filename = f"WORKING_screenshot_{timestamp}.png" + +# Take screenshot - this should now show our red frame +result = automation.screenshot(filename) +print(f"Screenshot taken: {filename} - Result: {result}") + +# Test clicking on the frame +automation.click(200, 200) # Click in center of red frame + +# Step to process the click +mcrfpy.step(0.1) + +# Test keyboard input +automation.typewrite("Hello from step-based test!") + +# Step to process keyboard input +mcrfpy.step(0.1) + +# Take another screenshot to show any changes +filename2 = f"WORKING_screenshot_after_click_{timestamp}.png" +automation.screenshot(filename2) +print(f"Second screenshot: {filename2}") + +print("Test completed successfully!") +print("\nThis works because:") +print("1. mcrfpy.step() advances simulation synchronously") +print("2. The scene renders during step() calls") +print("3. The RenderTexture contains actual rendered content") + +print("PASS") +sys.exit(0) diff --git a/tests/unit/test_color_helpers.py b/tests/unit/test_color_helpers.py index 795ee31..7cc1512 100644 --- a/tests/unit/test_color_helpers.py +++ b/tests/unit/test_color_helpers.py @@ -1,182 +1,181 @@ #!/usr/bin/env python3 """ Test #94: Color helper methods - from_hex, to_hex, lerp +Refactored to use mcrfpy.step() for synchronous execution. """ import mcrfpy import sys -def test_color_helpers(timer, runtime): - """Test Color helper methods""" - - all_pass = True - - # Test 1: from_hex with # prefix - try: - c1 = mcrfpy.Color.from_hex("#FF0000") - assert c1.r == 255 and c1.g == 0 and c1.b == 0 and c1.a == 255, f"from_hex('#FF0000') failed: {c1}" - print("+ Color.from_hex('#FF0000') works") - except Exception as e: - print(f"x Color.from_hex('#FF0000') failed: {e}") - all_pass = False - - # Test 2: from_hex without # prefix - try: - c2 = mcrfpy.Color.from_hex("00FF00") - assert c2.r == 0 and c2.g == 255 and c2.b == 0 and c2.a == 255, f"from_hex('00FF00') failed: {c2}" - print("+ Color.from_hex('00FF00') works") - except Exception as e: - print(f"x Color.from_hex('00FF00') failed: {e}") - all_pass = False - - # Test 3: from_hex with alpha - try: - c3 = mcrfpy.Color.from_hex("#0000FF80") - assert c3.r == 0 and c3.g == 0 and c3.b == 255 and c3.a == 128, f"from_hex('#0000FF80') failed: {c3}" - print("+ Color.from_hex('#0000FF80') with alpha works") - except Exception as e: - print(f"x Color.from_hex('#0000FF80') failed: {e}") - all_pass = False - - # Test 4: from_hex error handling - try: - c4 = mcrfpy.Color.from_hex("GGGGGG") - print("x from_hex should fail on invalid hex") - all_pass = False - except ValueError as e: - print("+ Color.from_hex() correctly rejects invalid hex") - - # Test 5: from_hex wrong length - try: - c5 = mcrfpy.Color.from_hex("FF00") - print("x from_hex should fail on wrong length") - all_pass = False - except ValueError as e: - print("+ Color.from_hex() correctly rejects wrong length") - - # Test 6: to_hex without alpha - try: - c6 = mcrfpy.Color(255, 128, 64) - hex_str = c6.to_hex() - assert hex_str == "#FF8040", f"to_hex() failed: {hex_str}" - print("+ Color.to_hex() works") - except Exception as e: - print(f"x Color.to_hex() failed: {e}") - all_pass = False - - # Test 7: to_hex with alpha - try: - c7 = mcrfpy.Color(255, 128, 64, 127) - hex_str = c7.to_hex() - assert hex_str == "#FF80407F", f"to_hex() with alpha failed: {hex_str}" - print("+ Color.to_hex() with alpha works") - except Exception as e: - print(f"x Color.to_hex() with alpha failed: {e}") - all_pass = False - - # Test 8: Round-trip hex conversion - try: - original_hex = "#ABCDEF" - c8 = mcrfpy.Color.from_hex(original_hex) - result_hex = c8.to_hex() - assert result_hex == original_hex, f"Round-trip failed: {original_hex} -> {result_hex}" - print("+ Hex round-trip conversion works") - except Exception as e: - print(f"x Hex round-trip failed: {e}") - all_pass = False - - # Test 9: lerp at t=0 - try: - red = mcrfpy.Color(255, 0, 0) - blue = mcrfpy.Color(0, 0, 255) - result = red.lerp(blue, 0.0) - assert result.r == 255 and result.g == 0 and result.b == 0, f"lerp(t=0) failed: {result}" - print("+ Color.lerp(t=0) returns start color") - except Exception as e: - print(f"x Color.lerp(t=0) failed: {e}") - all_pass = False - - # Test 10: lerp at t=1 - try: - red = mcrfpy.Color(255, 0, 0) - blue = mcrfpy.Color(0, 0, 255) - result = red.lerp(blue, 1.0) - assert result.r == 0 and result.g == 0 and result.b == 255, f"lerp(t=1) failed: {result}" - print("+ Color.lerp(t=1) returns end color") - except Exception as e: - print(f"x Color.lerp(t=1) failed: {e}") - all_pass = False - - # Test 11: lerp at t=0.5 - try: - red = mcrfpy.Color(255, 0, 0) - blue = mcrfpy.Color(0, 0, 255) - result = red.lerp(blue, 0.5) - # Expect roughly (127, 0, 127) - assert 126 <= result.r <= 128 and result.g == 0 and 126 <= result.b <= 128, f"lerp(t=0.5) failed: {result}" - print("+ Color.lerp(t=0.5) returns midpoint") - except Exception as e: - print(f"x Color.lerp(t=0.5) failed: {e}") - all_pass = False - - # Test 12: lerp with alpha - try: - c1 = mcrfpy.Color(255, 0, 0, 255) - c2 = mcrfpy.Color(0, 255, 0, 0) - result = c1.lerp(c2, 0.5) - assert 126 <= result.r <= 128 and 126 <= result.g <= 128 and result.b == 0, f"lerp color components failed" - assert 126 <= result.a <= 128, f"lerp alpha failed: {result.a}" - print("+ Color.lerp() with alpha works") - except Exception as e: - print(f"x Color.lerp() with alpha failed: {e}") - all_pass = False - - # Test 13: lerp clamps t < 0 - try: - red = mcrfpy.Color(255, 0, 0) - blue = mcrfpy.Color(0, 0, 255) - result = red.lerp(blue, -0.5) - assert result.r == 255 and result.g == 0 and result.b == 0, f"lerp(t<0) should clamp to 0" - print("+ Color.lerp() clamps t < 0") - except Exception as e: - print(f"x Color.lerp(t<0) failed: {e}") - all_pass = False - - # Test 14: lerp clamps t > 1 - try: - red = mcrfpy.Color(255, 0, 0) - blue = mcrfpy.Color(0, 0, 255) - result = red.lerp(blue, 1.5) - assert result.r == 0 and result.g == 0 and result.b == 255, f"lerp(t>1) should clamp to 1" - print("+ Color.lerp() clamps t > 1") - except Exception as e: - print(f"x Color.lerp(t>1) failed: {e}") - all_pass = False - - # Test 15: Practical use case - gradient - try: - start = mcrfpy.Color.from_hex("#FF0000") # Red - end = mcrfpy.Color.from_hex("#0000FF") # Blue - - # Create 5-step gradient - steps = [] - for i in range(5): - t = i / 4.0 - color = start.lerp(end, t) - steps.append(color.to_hex()) - - assert steps[0] == "#FF0000", "Gradient start should be red" - assert steps[4] == "#0000FF", "Gradient end should be blue" - assert len(set(steps)) == 5, "All gradient steps should be unique" - - print("+ Gradient generation works correctly") - except Exception as e: - print(f"x Gradient generation failed: {e}") - all_pass = False - - print(f"\n{'PASS' if all_pass else 'FAIL'}") - sys.exit(0 if all_pass else 1) - -# Run test +# Initialize scene test = mcrfpy.Scene("test") -test_timer = mcrfpy.Timer("test", test_color_helpers, 100, once=True) \ No newline at end of file +test.activate() +mcrfpy.step(0.01) + +all_pass = True + +# Test 1: from_hex with # prefix +try: + c1 = mcrfpy.Color.from_hex("#FF0000") + assert c1.r == 255 and c1.g == 0 and c1.b == 0 and c1.a == 255, f"from_hex('#FF0000') failed: {c1}" + print("+ Color.from_hex('#FF0000') works") +except Exception as e: + print(f"x Color.from_hex('#FF0000') failed: {e}") + all_pass = False + +# Test 2: from_hex without # prefix +try: + c2 = mcrfpy.Color.from_hex("00FF00") + assert c2.r == 0 and c2.g == 255 and c2.b == 0 and c2.a == 255, f"from_hex('00FF00') failed: {c2}" + print("+ Color.from_hex('00FF00') works") +except Exception as e: + print(f"x Color.from_hex('00FF00') failed: {e}") + all_pass = False + +# Test 3: from_hex with alpha +try: + c3 = mcrfpy.Color.from_hex("#0000FF80") + assert c3.r == 0 and c3.g == 0 and c3.b == 255 and c3.a == 128, f"from_hex('#0000FF80') failed: {c3}" + print("+ Color.from_hex('#0000FF80') with alpha works") +except Exception as e: + print(f"x Color.from_hex('#0000FF80') failed: {e}") + all_pass = False + +# Test 4: from_hex error handling +try: + c4 = mcrfpy.Color.from_hex("GGGGGG") + print("x from_hex should fail on invalid hex") + all_pass = False +except ValueError as e: + print("+ Color.from_hex() correctly rejects invalid hex") + +# Test 5: from_hex wrong length +try: + c5 = mcrfpy.Color.from_hex("FF00") + print("x from_hex should fail on wrong length") + all_pass = False +except ValueError as e: + print("+ Color.from_hex() correctly rejects wrong length") + +# Test 6: to_hex without alpha +try: + c6 = mcrfpy.Color(255, 128, 64) + hex_str = c6.to_hex() + assert hex_str == "#FF8040", f"to_hex() failed: {hex_str}" + print("+ Color.to_hex() works") +except Exception as e: + print(f"x Color.to_hex() failed: {e}") + all_pass = False + +# Test 7: to_hex with alpha +try: + c7 = mcrfpy.Color(255, 128, 64, 127) + hex_str = c7.to_hex() + assert hex_str == "#FF80407F", f"to_hex() with alpha failed: {hex_str}" + print("+ Color.to_hex() with alpha works") +except Exception as e: + print(f"x Color.to_hex() with alpha failed: {e}") + all_pass = False + +# Test 8: Round-trip hex conversion +try: + original_hex = "#ABCDEF" + c8 = mcrfpy.Color.from_hex(original_hex) + result_hex = c8.to_hex() + assert result_hex == original_hex, f"Round-trip failed: {original_hex} -> {result_hex}" + print("+ Hex round-trip conversion works") +except Exception as e: + print(f"x Hex round-trip failed: {e}") + all_pass = False + +# Test 9: lerp at t=0 +try: + red = mcrfpy.Color(255, 0, 0) + blue = mcrfpy.Color(0, 0, 255) + result = red.lerp(blue, 0.0) + assert result.r == 255 and result.g == 0 and result.b == 0, f"lerp(t=0) failed: {result}" + print("+ Color.lerp(t=0) returns start color") +except Exception as e: + print(f"x Color.lerp(t=0) failed: {e}") + all_pass = False + +# Test 10: lerp at t=1 +try: + red = mcrfpy.Color(255, 0, 0) + blue = mcrfpy.Color(0, 0, 255) + result = red.lerp(blue, 1.0) + assert result.r == 0 and result.g == 0 and result.b == 255, f"lerp(t=1) failed: {result}" + print("+ Color.lerp(t=1) returns end color") +except Exception as e: + print(f"x Color.lerp(t=1) failed: {e}") + all_pass = False + +# Test 11: lerp at t=0.5 +try: + red = mcrfpy.Color(255, 0, 0) + blue = mcrfpy.Color(0, 0, 255) + result = red.lerp(blue, 0.5) + # Expect roughly (127, 0, 127) + assert 126 <= result.r <= 128 and result.g == 0 and 126 <= result.b <= 128, f"lerp(t=0.5) failed: {result}" + print("+ Color.lerp(t=0.5) returns midpoint") +except Exception as e: + print(f"x Color.lerp(t=0.5) failed: {e}") + all_pass = False + +# Test 12: lerp with alpha +try: + c1 = mcrfpy.Color(255, 0, 0, 255) + c2 = mcrfpy.Color(0, 255, 0, 0) + result = c1.lerp(c2, 0.5) + assert 126 <= result.r <= 128 and 126 <= result.g <= 128 and result.b == 0, f"lerp color components failed" + assert 126 <= result.a <= 128, f"lerp alpha failed: {result.a}" + print("+ Color.lerp() with alpha works") +except Exception as e: + print(f"x Color.lerp() with alpha failed: {e}") + all_pass = False + +# Test 13: lerp clamps t < 0 +try: + red = mcrfpy.Color(255, 0, 0) + blue = mcrfpy.Color(0, 0, 255) + result = red.lerp(blue, -0.5) + assert result.r == 255 and result.g == 0 and result.b == 0, f"lerp(t<0) should clamp to 0" + print("+ Color.lerp() clamps t < 0") +except Exception as e: + print(f"x Color.lerp(t<0) failed: {e}") + all_pass = False + +# Test 14: lerp clamps t > 1 +try: + red = mcrfpy.Color(255, 0, 0) + blue = mcrfpy.Color(0, 0, 255) + result = red.lerp(blue, 1.5) + assert result.r == 0 and result.g == 0 and result.b == 255, f"lerp(t>1) should clamp to 1" + print("+ Color.lerp() clamps t > 1") +except Exception as e: + print(f"x Color.lerp(t>1) failed: {e}") + all_pass = False + +# Test 15: Practical use case - gradient +try: + start = mcrfpy.Color.from_hex("#FF0000") # Red + end = mcrfpy.Color.from_hex("#0000FF") # Blue + + # Create 5-step gradient + steps = [] + for i in range(5): + t = i / 4.0 + color = start.lerp(end, t) + steps.append(color.to_hex()) + + assert steps[0] == "#FF0000", "Gradient start should be red" + assert steps[4] == "#0000FF", "Gradient end should be blue" + assert len(set(steps)) == 5, "All gradient steps should be unique" + + print("+ Gradient generation works correctly") +except Exception as e: + print(f"x Gradient generation failed: {e}") + all_pass = False + +print(f"\n{'PASS' if all_pass else 'FAIL'}") +sys.exit(0 if all_pass else 1) diff --git a/tests/unit/test_empty_animation_manager.py b/tests/unit/test_empty_animation_manager.py index 225bbde..46ef106 100644 --- a/tests/unit/test_empty_animation_manager.py +++ b/tests/unit/test_empty_animation_manager.py @@ -1,20 +1,28 @@ #!/usr/bin/env python3 """ Test if AnimationManager crashes with no animations +Refactored to use mcrfpy.step() for synchronous execution. """ import mcrfpy +import sys print("Creating empty scene...") test = mcrfpy.Scene("test") test.activate() print("Scene created, no animations added") -print("Starting game loop in 100ms...") +print("Advancing simulation with step()...") -def check_alive(timer, runtime): - print(f"Timer fired at {runtime}ms - AnimationManager survived!") - mcrfpy.Timer("exit", lambda t, r: mcrfpy.exit(), 100, once=True) +# Step multiple times to simulate game loop running +# If AnimationManager crashes with empty state, this will fail +try: + for i in range(10): + mcrfpy.step(0.1) # 10 steps of 0.1s = 1 second simulated -mcrfpy.Timer("check", check_alive, 1000, once=True) -print("If this crashes immediately, AnimationManager has an issue with empty state") + print("AnimationManager survived 10 steps with no animations!") + print("PASS") + sys.exit(0) +except Exception as e: + print(f"FAIL: AnimationManager crashed: {e}") + sys.exit(1) diff --git a/tests/unit/test_frame_clipping.py b/tests/unit/test_frame_clipping.py index 0ec5c09..1a93b75 100644 --- a/tests/unit/test_frame_clipping.py +++ b/tests/unit/test_frame_clipping.py @@ -1,135 +1,118 @@ #!/usr/bin/env python3 -"""Test UIFrame clipping functionality""" +"""Test UIFrame clipping functionality +Refactored to use mcrfpy.step() for synchronous execution. +""" import mcrfpy -from mcrfpy import Color, Frame, Caption +from mcrfpy import Color, Frame, Caption, automation import sys -# Module-level state to avoid closures -_test_state = {} - -def take_second_screenshot(timer, runtime): - """Take final screenshot and exit""" - timer.stop() - from mcrfpy import automation - automation.screenshot("frame_clipping_animated.png") - print("\nTest completed successfully!") - print("Screenshots saved:") - print(" - frame_clipping_test.png (initial state)") - print(" - frame_clipping_animated.png (with animation)") - sys.exit(0) - -def animate_frames(timer, runtime): - """Animate frames to demonstrate clipping""" - timer.stop() - scene = test.children - # Move child frames - parent1 = scene[0] - parent2 = scene[1] - parent1.children[1].x = 50 - parent2.children[1].x = 50 - global screenshot2_timer - screenshot2_timer = mcrfpy.Timer("screenshot2", take_second_screenshot, 500, once=True) - -def test_clipping(timer, runtime): - """Test that clip_children property works correctly""" - timer.stop() - - print("Testing UIFrame clipping functionality...") - - scene = test.children - - # Create parent frame with clipping disabled (default) - parent1 = Frame(pos=(50, 50), size=(200, 150), - fill_color=Color(100, 100, 200), - outline_color=Color(255, 255, 255), - outline=2) - parent1.name = "parent1" - scene.append(parent1) - - # Create parent frame with clipping enabled - parent2 = Frame(pos=(300, 50), size=(200, 150), - fill_color=Color(200, 100, 100), - outline_color=Color(255, 255, 255), - outline=2) - parent2.name = "parent2" - parent2.clip_children = True - scene.append(parent2) - - # Add captions to both frames - caption1 = Caption(text="This text should overflow the frame bounds", pos=(10, 10)) - caption1.font_size = 16 - caption1.fill_color = Color(255, 255, 255) - parent1.children.append(caption1) - - caption2 = Caption(text="This text should be clipped to frame bounds", pos=(10, 10)) - caption2.font_size = 16 - caption2.fill_color = Color(255, 255, 255) - parent2.children.append(caption2) - - # Add child frames that extend beyond parent bounds - child1 = Frame(pos=(150, 100), size=(100, 100), - fill_color=Color(50, 255, 50), - outline_color=Color(0, 0, 0), - outline=1) - parent1.children.append(child1) - - child2 = Frame(pos=(150, 100), size=(100, 100), - fill_color=Color(50, 255, 50), - outline_color=Color(0, 0, 0), - outline=1) - parent2.children.append(child2) - - # Add caption to show clip state - status = Caption(text=f"Left frame: clip_children={parent1.clip_children}\n" - f"Right frame: clip_children={parent2.clip_children}", - pos=(50, 250)) - status.font_size = 14 - status.fill_color = Color(255, 255, 255) - scene.append(status) - - # Add instructions - instructions = Caption(text="Left: Children should overflow (no clipping)\n" - "Right: Children should be clipped to frame bounds\n" - "Press 'c' to toggle clipping on left frame", - pos=(50, 300)) - instructions.font_size = 12 - instructions.fill_color = Color(200, 200, 200) - scene.append(instructions) - - # Take screenshot - from mcrfpy import automation - automation.screenshot("frame_clipping_test.png") - - print(f"Parent1 clip_children: {parent1.clip_children}") - print(f"Parent2 clip_children: {parent2.clip_children}") - - # Test toggling clip_children - parent1.clip_children = True - print(f"After toggle - Parent1 clip_children: {parent1.clip_children}") - - # Verify the property setter works - try: - parent1.clip_children = "not a bool" - print("ERROR: clip_children accepted non-boolean value") - except TypeError as e: - print(f"PASS: clip_children correctly rejected non-boolean: {e}") - - # Start animation after a short delay - global animate_timer - animate_timer = mcrfpy.Timer("animate", animate_frames, 100, once=True) - -def handle_keypress(key, modifiers): - if key == "c": - scene = test.children - parent1 = scene[0] - parent1.clip_children = not parent1.clip_children - print(f"Toggled parent1 clip_children to: {parent1.clip_children}") - -# Main execution print("Creating test scene...") test = mcrfpy.Scene("test") test.activate() -test.on_key = handle_keypress -test_clipping_timer = mcrfpy.Timer("test_clipping", test_clipping, 100, once=True) -print("Test scheduled, running...") +mcrfpy.step(0.01) # Initialize + +print("Testing UIFrame clipping functionality...") + +scene = test.children + +# Create parent frame with clipping disabled (default) +parent1 = Frame(pos=(50, 50), size=(200, 150), + fill_color=Color(100, 100, 200), + outline_color=Color(255, 255, 255), + outline=2) +parent1.name = "parent1" +scene.append(parent1) + +# Create parent frame with clipping enabled +parent2 = Frame(pos=(300, 50), size=(200, 150), + fill_color=Color(200, 100, 100), + outline_color=Color(255, 255, 255), + outline=2) +parent2.name = "parent2" +parent2.clip_children = True +scene.append(parent2) + +# Add captions to both frames +caption1 = Caption(text="This text should overflow the frame bounds", pos=(10, 10)) +caption1.font_size = 16 +caption1.fill_color = Color(255, 255, 255) +parent1.children.append(caption1) + +caption2 = Caption(text="This text should be clipped to frame bounds", pos=(10, 10)) +caption2.font_size = 16 +caption2.fill_color = Color(255, 255, 255) +parent2.children.append(caption2) + +# Add child frames that extend beyond parent bounds +child1 = Frame(pos=(150, 100), size=(100, 100), + fill_color=Color(50, 255, 50), + outline_color=Color(0, 0, 0), + outline=1) +parent1.children.append(child1) + +child2 = Frame(pos=(150, 100), size=(100, 100), + fill_color=Color(50, 255, 50), + outline_color=Color(0, 0, 0), + outline=1) +parent2.children.append(child2) + +# Add caption to show clip state +status = Caption(text=f"Left frame: clip_children={parent1.clip_children}\n" + f"Right frame: clip_children={parent2.clip_children}", + pos=(50, 250)) +status.font_size = 14 +status.fill_color = Color(255, 255, 255) +scene.append(status) + +# Add instructions +instructions = Caption(text="Left: Children should overflow (no clipping)\n" + "Right: Children should be clipped to frame bounds", + pos=(50, 300)) +instructions.font_size = 12 +instructions.fill_color = Color(200, 200, 200) +scene.append(instructions) + +# Step to render +mcrfpy.step(0.1) + +# Take screenshot +automation.screenshot("frame_clipping_test.png") + +print(f"Parent1 clip_children: {parent1.clip_children}") +print(f"Parent2 clip_children: {parent2.clip_children}") + +# Test toggling clip_children +parent1.clip_children = True +print(f"After toggle - Parent1 clip_children: {parent1.clip_children}") + +# Verify the property setter works +test_passed = True +try: + parent1.clip_children = "not a bool" + print("ERROR: clip_children accepted non-boolean value") + test_passed = False +except TypeError as e: + print(f"PASS: clip_children correctly rejected non-boolean: {e}") + +# Animate frames (move children) +parent1.children[1].x = 50 +parent2.children[1].x = 50 + +# Step to render animation +mcrfpy.step(0.1) + +# Take second screenshot +automation.screenshot("frame_clipping_animated.png") + +print("\nTest completed successfully!") +print("Screenshots saved:") +print(" - frame_clipping_test.png (initial state)") +print(" - frame_clipping_animated.png (with animation)") + +if test_passed: + print("PASS") + sys.exit(0) +else: + print("FAIL") + sys.exit(1) diff --git a/tests/unit/test_frame_clipping_advanced.py b/tests/unit/test_frame_clipping_advanced.py index 5c18331..769986b 100644 --- a/tests/unit/test_frame_clipping_advanced.py +++ b/tests/unit/test_frame_clipping_advanced.py @@ -1,105 +1,95 @@ #!/usr/bin/env python3 -"""Advanced test for UIFrame clipping with nested frames""" +"""Advanced test for UIFrame clipping with nested frames +Refactored to use mcrfpy.step() for synchronous execution. +""" import mcrfpy -from mcrfpy import Color, Frame, Caption, Vector +from mcrfpy import Color, Frame, Caption, Vector, automation import sys -def test_nested_clipping(timer, runtime): - """Test nested frames with clipping""" - timer.stop() - - print("Testing advanced UIFrame clipping with nested frames...") - - # Create test scene - scene = test.children - - # Create outer frame with clipping enabled - outer = Frame(pos=(50, 50), size=(400, 300), - fill_color=Color(50, 50, 150), - outline_color=Color(255, 255, 255), - outline=3) - outer.name = "outer" - outer.clip_children = True - scene.append(outer) - - # Create inner frame that extends beyond outer bounds - inner = Frame(pos=(200, 150), size=(300, 200), - fill_color=Color(150, 50, 50), - outline_color=Color(255, 255, 0), - outline=2) - inner.name = "inner" - inner.clip_children = True # Also enable clipping on inner frame - outer.children.append(inner) - - # Add content to inner frame that extends beyond its bounds - for i in range(5): - caption = Caption(text=f"Line {i+1}: This text should be double-clipped", pos=(10, 30 * i)) - caption.font_size = 14 - caption.fill_color = Color(255, 255, 255) - inner.children.append(caption) - - # Add a child frame to inner that extends way out - deeply_nested = Frame(pos=(250, 100), size=(200, 150), - fill_color=Color(50, 150, 50), - outline_color=Color(255, 0, 255), - outline=2) - deeply_nested.name = "deeply_nested" - inner.children.append(deeply_nested) - - # Add status text - status = Caption(text="Nested clipping test:\n" - "- Blue outer frame clips red inner frame\n" - "- Red inner frame clips green deeply nested frame\n" - "- All text should be clipped to frame bounds", - pos=(50, 380)) - status.font_size = 12 - status.fill_color = Color(200, 200, 200) - scene.append(status) - - # Test render texture size handling - print(f"Outer frame size: {outer.w}x{outer.h}") - print(f"Inner frame size: {inner.w}x{inner.h}") - - # Dynamically resize frames to test RenderTexture recreation - def resize_test(timer, runtime): - timer.stop() - print("Resizing frames to test RenderTexture recreation...") - outer.w = 450 - outer.h = 350 - inner.w = 350 - inner.h = 250 - print(f"New outer frame size: {outer.w}x{outer.h}") - print(f"New inner frame size: {inner.w}x{inner.h}") - - # Take screenshot after resize - global screenshot_resize_timer - screenshot_resize_timer = mcrfpy.Timer("screenshot_resize", take_resize_screenshot, 500, once=True) - - def take_resize_screenshot(timer, runtime): - timer.stop() - from mcrfpy import automation - automation.screenshot("frame_clipping_resized.png") - print("\nAdvanced test completed!") - print("Screenshots saved:") - print(" - frame_clipping_resized.png (after resize)") - sys.exit(0) - - # Take initial screenshot - from mcrfpy import automation - automation.screenshot("frame_clipping_nested.png") - print("Initial screenshot saved: frame_clipping_nested.png") - - # Schedule resize test - global resize_test_timer - resize_test_timer = mcrfpy.Timer("resize_test", resize_test, 1000, once=True) - -# Main execution print("Creating advanced test scene...") test = mcrfpy.Scene("test") test.activate() +mcrfpy.step(0.01) -# Schedule the test -test_nested_clipping_timer = mcrfpy.Timer("test_nested_clipping", test_nested_clipping, 100, once=True) +print("Testing advanced UIFrame clipping with nested frames...") -print("Advanced test scheduled, running...") \ No newline at end of file +# Create test scene +scene = test.children + +# Create outer frame with clipping enabled +outer = Frame(pos=(50, 50), size=(400, 300), + fill_color=Color(50, 50, 150), + outline_color=Color(255, 255, 255), + outline=3) +outer.name = "outer" +outer.clip_children = True +scene.append(outer) + +# Create inner frame that extends beyond outer bounds +inner = Frame(pos=(200, 150), size=(300, 200), + fill_color=Color(150, 50, 50), + outline_color=Color(255, 255, 0), + outline=2) +inner.name = "inner" +inner.clip_children = True # Also enable clipping on inner frame +outer.children.append(inner) + +# Add content to inner frame that extends beyond its bounds +for i in range(5): + caption = Caption(text=f"Line {i+1}: This text should be double-clipped", pos=(10, 30 * i)) + caption.font_size = 14 + caption.fill_color = Color(255, 255, 255) + inner.children.append(caption) + +# Add a child frame to inner that extends way out +deeply_nested = Frame(pos=(250, 100), size=(200, 150), + fill_color=Color(50, 150, 50), + outline_color=Color(255, 0, 255), + outline=2) +deeply_nested.name = "deeply_nested" +inner.children.append(deeply_nested) + +# Add status text +status = Caption(text="Nested clipping test:\n" + "- Blue outer frame clips red inner frame\n" + "- Red inner frame clips green deeply nested frame\n" + "- All text should be clipped to frame bounds", + pos=(50, 380)) +status.font_size = 12 +status.fill_color = Color(200, 200, 200) +scene.append(status) + +# Test render texture size handling +print(f"Outer frame size: {outer.w}x{outer.h}") +print(f"Inner frame size: {inner.w}x{inner.h}") + +# Step to render +mcrfpy.step(0.1) + +# Take initial screenshot +automation.screenshot("frame_clipping_nested.png") +print("Initial screenshot saved: frame_clipping_nested.png") + +# Dynamically resize frames to test RenderTexture recreation +print("Resizing frames to test RenderTexture recreation...") +outer.w = 450 +outer.h = 350 +inner.w = 350 +inner.h = 250 +print(f"New outer frame size: {outer.w}x{outer.h}") +print(f"New inner frame size: {inner.w}x{inner.h}") + +# Step to render resize +mcrfpy.step(0.1) + +# Take screenshot after resize +automation.screenshot("frame_clipping_resized.png") + +print("\nAdvanced test completed!") +print("Screenshots saved:") +print(" - frame_clipping_nested.png (initial)") +print(" - frame_clipping_resized.png (after resize)") + +print("PASS") +sys.exit(0) diff --git a/tests/unit/test_grid_children.py b/tests/unit/test_grid_children.py index 306f8d9..9ffd5e4 100644 --- a/tests/unit/test_grid_children.py +++ b/tests/unit/test_grid_children.py @@ -1,129 +1,125 @@ #!/usr/bin/env python3 -"""Test Grid.children collection - Issue #132""" +"""Test Grid.children collection - Issue #132 +Refactored to use mcrfpy.step() for synchronous execution. +""" import mcrfpy from mcrfpy import automation import sys -def take_screenshot(timer, runtime): - """Take screenshot after render completes""" - timer.stop() - automation.screenshot("test_grid_children_result.png") - - print("Screenshot saved to test_grid_children_result.png") - print("PASS - Grid.children test completed") - sys.exit(0) - -def run_test(timer, runtime): - """Main test - runs after scene is set up""" - timer.stop() - - # Get the scene UI - ui = test.children - - # Create a grid without texture (uses default 16x16 cells) - print("Test 1: Creating Grid with children...") - grid = mcrfpy.Grid(grid_size=(20, 15), pos=(50, 50), size=(320, 240)) - grid.fill_color = mcrfpy.Color(30, 30, 60) - ui.append(grid) - - # Verify entities and children properties exist - print(f" grid.entities = {grid.entities}") - print(f" grid.children = {grid.children}") - - # Test 2: Add UIDrawable children to the grid - print("\nTest 2: Adding UIDrawable children...") - - # Speech bubble style caption - positioned in grid-world pixels - # At cell (5, 3) which is 5*16=80, 3*16=48 in pixels - caption = mcrfpy.Caption(text="Hello!", pos=(80, 48)) - caption.fill_color = mcrfpy.Color(255, 255, 200) - caption.outline = 1 - caption.outline_color = mcrfpy.Color(0, 0, 0) - grid.children.append(caption) - print(f" Added caption at (80, 48)") - - # A highlight circle around cell (10, 7) = (160, 112) - # Circle needs center, not pos - circle = mcrfpy.Circle(center=(168, 120), radius=20, - fill_color=mcrfpy.Color(255, 255, 0, 100), - outline_color=mcrfpy.Color(255, 255, 0), - outline=2) - grid.children.append(circle) - print(f" Added highlight circle at (168, 120)") - - # A line indicating a path from (2,2) to (8,6) - # In pixels: (32, 32) to (128, 96) - line = mcrfpy.Line(start=(32, 32), end=(128, 96), - color=mcrfpy.Color(0, 255, 0), thickness=3) - grid.children.append(line) - print(f" Added path line from (32,32) to (128,96)") - - # An arc for range indicator at (15, 10) = (240, 160) - arc = mcrfpy.Arc(center=(240, 160), radius=40, start_angle=0, end_angle=270, - color=mcrfpy.Color(255, 0, 255), thickness=4) - grid.children.append(arc) - print(f" Added range arc at (240, 160)") - - # Test 3: Verify children count - print(f"\nTest 3: Verifying children count...") - print(f" grid.children count = {len(grid.children)}") - assert len(grid.children) == 4, f"Expected 4 children, got {len(grid.children)}" - - # Test 4: Children should be accessible by index - print("\nTest 4: Accessing children by index...") - child0 = grid.children[0] - print(f" grid.children[0] = {child0}") - child1 = grid.children[1] - print(f" grid.children[1] = {child1}") - - # Test 5: Modify a child's position (should update in grid) - print("\nTest 5: Modifying child position...") - original_pos = (caption.pos.x, caption.pos.y) - caption.pos = mcrfpy.Vector(90, 58) - new_pos = (caption.pos.x, caption.pos.y) - print(f" Moved caption from {original_pos} to {new_pos}") - - # Test 6: Test z_index for children - print("\nTest 6: Testing z_index ordering...") - # Add overlapping elements with different z_index - frame1 = mcrfpy.Frame(pos=(150, 80), size=(40, 40)) - frame1.fill_color = mcrfpy.Color(255, 0, 0, 200) - frame1.z_index = 10 - grid.children.append(frame1) - - frame2 = mcrfpy.Frame(pos=(160, 90), size=(40, 40)) - frame2.fill_color = mcrfpy.Color(0, 255, 0, 200) - frame2.z_index = 5 # Lower z_index, rendered first (behind) - grid.children.append(frame2) - print(f" Added overlapping frames: red z=10, green z=5") - - # Test 7: Test visibility - print("\nTest 7: Testing child visibility...") - frame3 = mcrfpy.Frame(pos=(50, 150), size=(30, 30)) - frame3.fill_color = mcrfpy.Color(0, 0, 255) - frame3.visible = False - grid.children.append(frame3) - print(f" Added invisible blue frame (should not appear)") - - # Test 8: Pan the grid and verify children move with it - print("\nTest 8: Testing pan (children should follow grid camera)...") - # Center the view on cell (10, 7.5) - default was grid center - grid.center = (160, 120) # Center on pixel (160, 120) - print(f" Centered grid on (160, 120)") - - # Test 9: Test zoom - print("\nTest 9: Testing zoom...") - grid.zoom = 1.5 - print(f" Set zoom to 1.5") - - print(f"\nFinal children count: {len(grid.children)}") - - # Schedule screenshot for next frame - mcrfpy.Timer("screenshot", take_screenshot, 100, once=True) - -# Create a test scene +print("Creating test scene...") test = mcrfpy.Scene("test") test.activate() +mcrfpy.step(0.01) # Initialize -# Schedule test to run after game loop starts -mcrfpy.Timer("test", run_test, 50, once=True) +# Get the scene UI +ui = test.children + +# Test 1: Creating Grid with children +print("Test 1: Creating Grid with children...") +grid = mcrfpy.Grid(grid_size=(20, 15), pos=(50, 50), size=(320, 240)) +grid.fill_color = mcrfpy.Color(30, 30, 60) +ui.append(grid) + +# Verify entities and children properties exist +print(f" grid.entities = {grid.entities}") +print(f" grid.children = {grid.children}") + +# Test 2: Add UIDrawable children to the grid +print("\nTest 2: Adding UIDrawable children...") + +# Speech bubble style caption - positioned in grid-world pixels +# At cell (5, 3) which is 5*16=80, 3*16=48 in pixels +caption = mcrfpy.Caption(text="Hello!", pos=(80, 48)) +caption.fill_color = mcrfpy.Color(255, 255, 200) +caption.outline = 1 +caption.outline_color = mcrfpy.Color(0, 0, 0) +grid.children.append(caption) +print(f" Added caption at (80, 48)") + +# A highlight circle around cell (10, 7) = (160, 112) +# Circle needs center, not pos +circle = mcrfpy.Circle(center=(168, 120), radius=20, + fill_color=mcrfpy.Color(255, 255, 0, 100), + outline_color=mcrfpy.Color(255, 255, 0), + outline=2) +grid.children.append(circle) +print(f" Added highlight circle at (168, 120)") + +# A line indicating a path from (2,2) to (8,6) +# In pixels: (32, 32) to (128, 96) +line = mcrfpy.Line(start=(32, 32), end=(128, 96), + color=mcrfpy.Color(0, 255, 0), thickness=3) +grid.children.append(line) +print(f" Added path line from (32,32) to (128,96)") + +# An arc for range indicator at (15, 10) = (240, 160) +arc = mcrfpy.Arc(center=(240, 160), radius=40, start_angle=0, end_angle=270, + color=mcrfpy.Color(255, 0, 255), thickness=4) +grid.children.append(arc) +print(f" Added range arc at (240, 160)") + +# Test 3: Verify children count +print(f"\nTest 3: Verifying children count...") +print(f" grid.children count = {len(grid.children)}") +if len(grid.children) != 4: + print(f"FAIL: Expected 4 children, got {len(grid.children)}") + sys.exit(1) + +# Test 4: Children should be accessible by index +print("\nTest 4: Accessing children by index...") +child0 = grid.children[0] +print(f" grid.children[0] = {child0}") +child1 = grid.children[1] +print(f" grid.children[1] = {child1}") + +# Test 5: Modify a child's position (should update in grid) +print("\nTest 5: Modifying child position...") +original_pos = (caption.pos.x, caption.pos.y) +caption.pos = mcrfpy.Vector(90, 58) +new_pos = (caption.pos.x, caption.pos.y) +print(f" Moved caption from {original_pos} to {new_pos}") + +# Test 6: Test z_index for children +print("\nTest 6: Testing z_index ordering...") +# Add overlapping elements with different z_index +frame1 = mcrfpy.Frame(pos=(150, 80), size=(40, 40)) +frame1.fill_color = mcrfpy.Color(255, 0, 0, 200) +frame1.z_index = 10 +grid.children.append(frame1) + +frame2 = mcrfpy.Frame(pos=(160, 90), size=(40, 40)) +frame2.fill_color = mcrfpy.Color(0, 255, 0, 200) +frame2.z_index = 5 # Lower z_index, rendered first (behind) +grid.children.append(frame2) +print(f" Added overlapping frames: red z=10, green z=5") + +# Test 7: Test visibility +print("\nTest 7: Testing child visibility...") +frame3 = mcrfpy.Frame(pos=(50, 150), size=(30, 30)) +frame3.fill_color = mcrfpy.Color(0, 0, 255) +frame3.visible = False +grid.children.append(frame3) +print(f" Added invisible blue frame (should not appear)") + +# Test 8: Pan the grid and verify children move with it +print("\nTest 8: Testing pan (children should follow grid camera)...") +# Center the view on cell (10, 7.5) - default was grid center +grid.center = (160, 120) # Center on pixel (160, 120) +print(f" Centered grid on (160, 120)") + +# Test 9: Test zoom +print("\nTest 9: Testing zoom...") +grid.zoom = 1.5 +print(f" Set zoom to 1.5") + +print(f"\nFinal children count: {len(grid.children)}") + +# Step to render everything +mcrfpy.step(0.1) + +# Take screenshot +automation.screenshot("test_grid_children_result.png") +print("Screenshot saved to test_grid_children_result.png") + +print("PASS - Grid.children test completed") +sys.exit(0) diff --git a/tests/unit/test_no_arg_constructors.py b/tests/unit/test_no_arg_constructors.py index 1c884d3..c159030 100644 --- a/tests/unit/test_no_arg_constructors.py +++ b/tests/unit/test_no_arg_constructors.py @@ -2,90 +2,94 @@ """ Test that all UI classes can be instantiated without arguments. This verifies the fix for requiring arguments even with safe default constructors. +Refactored to use mcrfpy.step() for synchronous execution. """ import mcrfpy import sys +import traceback -def test_ui_constructors(timer, runtime): - """Test that UI classes can be instantiated without arguments""" - - print("Testing UI class instantiation without arguments...") - - # Test UICaption with no arguments - try: - caption = mcrfpy.Caption() - print("PASS: Caption() - Success") - print(f" Position: ({caption.x}, {caption.y})") - print(f" Text: '{caption.text}'") - assert caption.x == 0.0 - assert caption.y == 0.0 - assert caption.text == "" - except Exception as e: - print(f"FAIL: Caption() - {e}") - import traceback - traceback.print_exc() - - # Test UIFrame with no arguments - try: - frame = mcrfpy.Frame() - print("PASS: Frame() - Success") - print(f" Position: ({frame.x}, {frame.y})") - print(f" Size: ({frame.w}, {frame.h})") - assert frame.x == 0.0 - assert frame.y == 0.0 - assert frame.w == 0.0 - assert frame.h == 0.0 - except Exception as e: - print(f"FAIL: Frame() - {e}") - import traceback - traceback.print_exc() - - # Test UIGrid with no arguments - try: - grid = mcrfpy.Grid() - print("PASS: Grid() - Success") - print(f" Grid size: {grid.grid_x} x {grid.grid_y}") - print(f" Position: ({grid.x}, {grid.y})") - assert grid.grid_x == 1 - assert grid.grid_y == 1 - assert grid.x == 0.0 - assert grid.y == 0.0 - except Exception as e: - print(f"FAIL: Grid() - {e}") - import traceback - traceback.print_exc() - - # Test UIEntity with no arguments - try: - entity = mcrfpy.Entity() - print("PASS: Entity() - Success") - print(f" Position: ({entity.x}, {entity.y})") - assert entity.x == 0.0 - assert entity.y == 0.0 - except Exception as e: - print(f"FAIL: Entity() - {e}") - import traceback - traceback.print_exc() - - # Test UISprite with no arguments (if it has a default constructor) - try: - sprite = mcrfpy.Sprite() - print("PASS: Sprite() - Success") - print(f" Position: ({sprite.x}, {sprite.y})") - assert sprite.x == 0.0 - assert sprite.y == 0.0 - except Exception as e: - print(f"FAIL: Sprite() - {e}") - # Sprite might still require arguments, which is okay - - print("\nAll tests complete!") - - # Exit cleanly - sys.exit(0) - -# Create a basic scene so the game can start +# Initialize scene test = mcrfpy.Scene("test") +test.activate() +mcrfpy.step(0.01) -# Schedule the test to run after game initialization -test_timer = mcrfpy.Timer("test", test_ui_constructors, 100, once=True) \ No newline at end of file +print("Testing UI class instantiation without arguments...") + +all_pass = True + +# Test UICaption with no arguments +try: + caption = mcrfpy.Caption() + print("PASS: Caption() - Success") + print(f" Position: ({caption.x}, {caption.y})") + print(f" Text: '{caption.text}'") + assert caption.x == 0.0 + assert caption.y == 0.0 + assert caption.text == "" +except Exception as e: + print(f"FAIL: Caption() - {e}") + traceback.print_exc() + all_pass = False + +# Test UIFrame with no arguments +try: + frame = mcrfpy.Frame() + print("PASS: Frame() - Success") + print(f" Position: ({frame.x}, {frame.y})") + print(f" Size: ({frame.w}, {frame.h})") + assert frame.x == 0.0 + assert frame.y == 0.0 + assert frame.w == 0.0 + assert frame.h == 0.0 +except Exception as e: + print(f"FAIL: Frame() - {e}") + traceback.print_exc() + all_pass = False + +# Test UIGrid with no arguments +try: + grid = mcrfpy.Grid() + print("PASS: Grid() - Success") + print(f" Grid size: {grid.grid_x} x {grid.grid_y}") + print(f" Position: ({grid.x}, {grid.y})") + assert grid.grid_x == 1 + assert grid.grid_y == 1 + assert grid.x == 0.0 + assert grid.y == 0.0 +except Exception as e: + print(f"FAIL: Grid() - {e}") + traceback.print_exc() + all_pass = False + +# Test UIEntity with no arguments +try: + entity = mcrfpy.Entity() + print("PASS: Entity() - Success") + print(f" Position: ({entity.x}, {entity.y})") + assert entity.x == 0.0 + assert entity.y == 0.0 +except Exception as e: + print(f"FAIL: Entity() - {e}") + traceback.print_exc() + all_pass = False + +# Test UISprite with no arguments (if it has a default constructor) +try: + sprite = mcrfpy.Sprite() + print("PASS: Sprite() - Success") + print(f" Position: ({sprite.x}, {sprite.y})") + assert sprite.x == 0.0 + assert sprite.y == 0.0 +except Exception as e: + print(f"FAIL: Sprite() - {e}") + # Sprite might still require arguments, which is okay + +print("\nAll tests complete!") + +if all_pass: + print("PASS") + sys.exit(0) +else: + print("FAIL") + sys.exit(1) diff --git a/tests/unit/test_properties_quick.py b/tests/unit/test_properties_quick.py index 0fd6ee3..5c1e696 100644 --- a/tests/unit/test_properties_quick.py +++ b/tests/unit/test_properties_quick.py @@ -1,57 +1,67 @@ #!/usr/bin/env python3 -"""Quick test of drawable properties""" +"""Quick test of drawable properties +Refactored to use mcrfpy.step() for synchronous execution. +""" import mcrfpy import sys -def test_properties(timer, runtime): - timer.stop() - - print("\n=== Testing Properties ===") - - # Test Frame - try: - frame = mcrfpy.Frame(pos=(10, 10), size=(100, 100)) - print(f"Frame visible: {frame.visible}") - frame.visible = False - print(f"Frame visible after setting to False: {frame.visible}") - - print(f"Frame opacity: {frame.opacity}") - frame.opacity = 0.5 - print(f"Frame opacity after setting to 0.5: {frame.opacity}") - - bounds = frame.get_bounds() - print(f"Frame bounds: {bounds}") - - frame.move(5, 5) - bounds2 = frame.get_bounds() - print(f"Frame bounds after move(5,5): {bounds2}") - - print("✓ Frame properties work!") - except Exception as e: - print(f"✗ Frame failed: {e}") - - # Test Entity - try: - entity = mcrfpy.Entity() - print(f"\nEntity visible: {entity.visible}") - entity.visible = False - print(f"Entity visible after setting to False: {entity.visible}") - - print(f"Entity opacity: {entity.opacity}") - entity.opacity = 0.7 - print(f"Entity opacity after setting to 0.7: {entity.opacity}") - - bounds = entity.get_bounds() - print(f"Entity bounds: {bounds}") - - entity.move(3, 3) - print(f"Entity position after move(3,3): ({entity.x}, {entity.y})") - - print("✓ Entity properties work!") - except Exception as e: - print(f"✗ Entity failed: {e}") - - sys.exit(0) - +# Initialize scene test = mcrfpy.Scene("test") -test_properties_timer = mcrfpy.Timer("test_properties", test_properties, 100, once=True) \ No newline at end of file +test.activate() +mcrfpy.step(0.01) + +print("\n=== Testing Properties ===") + +all_pass = True + +# Test Frame +try: + frame = mcrfpy.Frame(pos=(10, 10), size=(100, 100)) + print(f"Frame visible: {frame.visible}") + frame.visible = False + print(f"Frame visible after setting to False: {frame.visible}") + + print(f"Frame opacity: {frame.opacity}") + frame.opacity = 0.5 + print(f"Frame opacity after setting to 0.5: {frame.opacity}") + + bounds = frame.get_bounds() + print(f"Frame bounds: {bounds}") + + frame.move(5, 5) + bounds2 = frame.get_bounds() + print(f"Frame bounds after move(5,5): {bounds2}") + + print("+ Frame properties work!") +except Exception as e: + print(f"x Frame failed: {e}") + all_pass = False + +# Test Entity +try: + entity = mcrfpy.Entity() + print(f"\nEntity visible: {entity.visible}") + entity.visible = False + print(f"Entity visible after setting to False: {entity.visible}") + + print(f"Entity opacity: {entity.opacity}") + entity.opacity = 0.7 + print(f"Entity opacity after setting to 0.7: {entity.opacity}") + + bounds = entity.get_bounds() + print(f"Entity bounds: {bounds}") + + entity.move(3, 3) + print(f"Entity position after move(3,3): ({entity.x}, {entity.y})") + + print("+ Entity properties work!") +except Exception as e: + print(f"x Entity failed: {e}") + all_pass = False + +if all_pass: + print("\nPASS") + sys.exit(0) +else: + print("\nFAIL") + sys.exit(1) diff --git a/tests/unit/test_python_object_cache.py b/tests/unit/test_python_object_cache.py index dbf83e3..d426cd4 100644 --- a/tests/unit/test_python_object_cache.py +++ b/tests/unit/test_python_object_cache.py @@ -4,6 +4,7 @@ Test for Python object cache - verifies that derived Python classes maintain their identity when stored in and retrieved from collections. Issue #112: Object Splitting - Preserve Python derived types in collections +Refactored to use mcrfpy.step() for synchronous execution. """ import mcrfpy @@ -16,136 +17,128 @@ test_results = [] def test(condition, message): global test_passed if condition: - test_results.append(f"✓ {message}") + test_results.append(f"+ {message}") else: - test_results.append(f"✗ {message}") + test_results.append(f"x {message}") test_passed = False -def run_tests(timer, runtime): - """Timer callback to run tests after game loop starts""" - global test_passed - - print("\n=== Testing Python Object Cache ===") - - # Test 1: Create derived Frame class - class MyFrame(mcrfpy.Frame): - def __init__(self, x=0, y=0): - super().__init__(pos=(x, y), size=(100, 100)) - self.custom_data = "I am a custom frame" - self.test_value = 42 - - # Test 2: Create instance and add to scene - frame = MyFrame(50, 50) - scene_ui = test_scene.children - scene_ui.append(frame) - - # Test 3: Retrieve from collection and check type - retrieved = scene_ui[0] - test(type(retrieved) == MyFrame, "Retrieved object maintains derived type") - test(isinstance(retrieved, MyFrame), "isinstance check passes") - test(hasattr(retrieved, 'custom_data'), "Custom attribute exists") - if hasattr(retrieved, 'custom_data'): - test(retrieved.custom_data == "I am a custom frame", "Custom attribute value preserved") - if hasattr(retrieved, 'test_value'): - test(retrieved.test_value == 42, "Numeric attribute value preserved") - - # Test 4: Check object identity (same Python object) - test(retrieved is frame, "Retrieved object is the same Python object") - test(id(retrieved) == id(frame), "Object IDs match") - - # Test 5: Multiple retrievals return same object - retrieved2 = scene_ui[0] - test(retrieved2 is retrieved, "Multiple retrievals return same object") - - # Test 6: Test with other UI types - class MySprite(mcrfpy.Sprite): - def __init__(self): - # Use default texture by passing None - super().__init__(texture=None, sprite_index=0) - self.sprite_data = "custom sprite" - - sprite = MySprite() - sprite.x = 200 - sprite.y = 200 - scene_ui.append(sprite) - - retrieved_sprite = scene_ui[1] - test(type(retrieved_sprite) == MySprite, "Sprite maintains derived type") - if hasattr(retrieved_sprite, 'sprite_data'): - test(retrieved_sprite.sprite_data == "custom sprite", "Sprite custom data preserved") - - # Test 7: Test with Caption - class MyCaption(mcrfpy.Caption): - def __init__(self, text): - # Use default font by passing None - super().__init__(text=text, font=None) - self.caption_id = "test_caption" - - caption = MyCaption("Test Caption") - caption.x = 10 - caption.y = 10 - scene_ui.append(caption) - - retrieved_caption = scene_ui[2] - test(type(retrieved_caption) == MyCaption, "Caption maintains derived type") - if hasattr(retrieved_caption, 'caption_id'): - test(retrieved_caption.caption_id == "test_caption", "Caption custom data preserved") - - # Test 8: Test removal and re-addition - # Use del to remove by index (Python standard), or .remove(element) to remove by value - print(f"before remove: {len(scene_ui)=}") - del scene_ui[-1] # Remove last element by index - print(f"after remove: {len(scene_ui)=}") - - scene_ui.append(frame) - retrieved3 = scene_ui[-1] # Get last element - test(retrieved3 is frame, "Object identity preserved after removal/re-addition") - - # Test 9: Test with Grid - class MyGrid(mcrfpy.Grid): - def __init__(self, w, h): - super().__init__(grid_size=(w, h)) - self.grid_name = "custom_grid" - - grid = MyGrid(10, 10) - grid.x = 300 - grid.y = 100 - scene_ui.append(grid) - - retrieved_grid = scene_ui[-1] - test(type(retrieved_grid) == MyGrid, "Grid maintains derived type") - if hasattr(retrieved_grid, 'grid_name'): - test(retrieved_grid.grid_name == "custom_grid", "Grid custom data preserved") - - # Test 10: Test with nested collections (Frame with children) - parent = MyFrame(400, 400) - child = MyFrame(10, 10) - child.custom_data = "I am a child" - parent.children.append(child) - scene_ui.append(parent) - - retrieved_parent = scene_ui[-1] - test(type(retrieved_parent) == MyFrame, "Parent frame maintains type") - if len(retrieved_parent.children) > 0: - retrieved_child = retrieved_parent.children[0] - test(type(retrieved_child) == MyFrame, "Child frame maintains type in nested collection") - if hasattr(retrieved_child, 'custom_data'): - test(retrieved_child.custom_data == "I am a child", "Child custom data preserved") - - # Print results - print("\n=== Test Results ===") - for result in test_results: - print(result) - - print(f"\n{'PASS' if test_passed else 'FAIL'}: {sum(1 for r in test_results if r.startswith('✓'))}/{len(test_results)} tests passed") - - sys.exit(0 if test_passed else 1) - # Create test scene test_scene = mcrfpy.Scene("test_scene") test_scene.activate() +mcrfpy.step(0.01) -# Schedule tests to run after game loop starts -test_timer = mcrfpy.Timer("test", run_tests, 100, once=True) +print("\n=== Testing Python Object Cache ===") -print("Python object cache test initialized. Running tests...") +# Test 1: Create derived Frame class +class MyFrame(mcrfpy.Frame): + def __init__(self, x=0, y=0): + super().__init__(pos=(x, y), size=(100, 100)) + self.custom_data = "I am a custom frame" + self.test_value = 42 + +# Test 2: Create instance and add to scene +frame = MyFrame(50, 50) +scene_ui = test_scene.children +scene_ui.append(frame) + +# Test 3: Retrieve from collection and check type +retrieved = scene_ui[0] +test(type(retrieved) == MyFrame, "Retrieved object maintains derived type") +test(isinstance(retrieved, MyFrame), "isinstance check passes") +test(hasattr(retrieved, 'custom_data'), "Custom attribute exists") +if hasattr(retrieved, 'custom_data'): + test(retrieved.custom_data == "I am a custom frame", "Custom attribute value preserved") +if hasattr(retrieved, 'test_value'): + test(retrieved.test_value == 42, "Numeric attribute value preserved") + +# Test 4: Check object identity (same Python object) +test(retrieved is frame, "Retrieved object is the same Python object") +test(id(retrieved) == id(frame), "Object IDs match") + +# Test 5: Multiple retrievals return same object +retrieved2 = scene_ui[0] +test(retrieved2 is retrieved, "Multiple retrievals return same object") + +# Test 6: Test with other UI types +class MySprite(mcrfpy.Sprite): + def __init__(self): + # Use default texture by passing None + super().__init__(texture=None, sprite_index=0) + self.sprite_data = "custom sprite" + +sprite = MySprite() +sprite.x = 200 +sprite.y = 200 +scene_ui.append(sprite) + +retrieved_sprite = scene_ui[1] +test(type(retrieved_sprite) == MySprite, "Sprite maintains derived type") +if hasattr(retrieved_sprite, 'sprite_data'): + test(retrieved_sprite.sprite_data == "custom sprite", "Sprite custom data preserved") + +# Test 7: Test with Caption +class MyCaption(mcrfpy.Caption): + def __init__(self, text): + # Use default font by passing None + super().__init__(text=text, font=None) + self.caption_id = "test_caption" + +caption = MyCaption("Test Caption") +caption.x = 10 +caption.y = 10 +scene_ui.append(caption) + +retrieved_caption = scene_ui[2] +test(type(retrieved_caption) == MyCaption, "Caption maintains derived type") +if hasattr(retrieved_caption, 'caption_id'): + test(retrieved_caption.caption_id == "test_caption", "Caption custom data preserved") + +# Test 8: Test removal and re-addition +# Use del to remove by index (Python standard), or .remove(element) to remove by value +print(f"before remove: {len(scene_ui)=}") +del scene_ui[-1] # Remove last element by index +print(f"after remove: {len(scene_ui)=}") + +scene_ui.append(frame) +retrieved3 = scene_ui[-1] # Get last element +test(retrieved3 is frame, "Object identity preserved after removal/re-addition") + +# Test 9: Test with Grid +class MyGrid(mcrfpy.Grid): + def __init__(self, w, h): + super().__init__(grid_size=(w, h)) + self.grid_name = "custom_grid" + +grid = MyGrid(10, 10) +grid.x = 300 +grid.y = 100 +scene_ui.append(grid) + +retrieved_grid = scene_ui[-1] +test(type(retrieved_grid) == MyGrid, "Grid maintains derived type") +if hasattr(retrieved_grid, 'grid_name'): + test(retrieved_grid.grid_name == "custom_grid", "Grid custom data preserved") + +# Test 10: Test with nested collections (Frame with children) +parent = MyFrame(400, 400) +child = MyFrame(10, 10) +child.custom_data = "I am a child" +parent.children.append(child) +scene_ui.append(parent) + +retrieved_parent = scene_ui[-1] +test(type(retrieved_parent) == MyFrame, "Parent frame maintains type") +if len(retrieved_parent.children) > 0: + retrieved_child = retrieved_parent.children[0] + test(type(retrieved_child) == MyFrame, "Child frame maintains type in nested collection") + if hasattr(retrieved_child, 'custom_data'): + test(retrieved_child.custom_data == "I am a child", "Child custom data preserved") + +# Print results +print("\n=== Test Results ===") +for result in test_results: + print(result) + +print(f"\n{'PASS' if test_passed else 'FAIL'}: {sum(1 for r in test_results if r.startswith('+'))}/{len(test_results)} tests passed") + +sys.exit(0 if test_passed else 1) diff --git a/tests/unit/test_simple_callback.py b/tests/unit/test_simple_callback.py index 7e7cd6a..18a403b 100644 --- a/tests/unit/test_simple_callback.py +++ b/tests/unit/test_simple_callback.py @@ -1,14 +1,32 @@ #!/usr/bin/env python3 -"""Very simple callback test""" +"""Very simple callback test - refactored to use mcrfpy.step()""" import mcrfpy import sys +callback_fired = False + def cb(a, t): + global callback_fired + callback_fired = True print("CB") +# Setup scene test = mcrfpy.Scene("test") test.activate() +mcrfpy.step(0.01) # Initialize + +# Create entity and animation e = mcrfpy.Entity((0, 0), texture=None, sprite_index=0) a = mcrfpy.Animation("x", 1.0, 0.1, "linear", callback=cb) a.start(e) -mcrfpy.Timer("exit", lambda t, r: sys.exit(0), 200, once=True) + +# Advance past animation duration (0.1s) +mcrfpy.step(0.15) + +# Verify callback fired +if callback_fired: + print("PASS: Callback fired") + sys.exit(0) +else: + print("FAIL: Callback did not fire") + sys.exit(1) diff --git a/tests/unit/test_simple_drawable.py b/tests/unit/test_simple_drawable.py index 63d37c3..36552b4 100644 --- a/tests/unit/test_simple_drawable.py +++ b/tests/unit/test_simple_drawable.py @@ -1,30 +1,32 @@ #!/usr/bin/env python3 -"""Simple test to isolate drawable issue""" +"""Simple test to isolate drawable issue +Refactored to use mcrfpy.step() for synchronous execution. +""" import mcrfpy import sys -def simple_test(timer, runtime): - timer.stop() - - try: - # Test basic functionality - frame = mcrfpy.Frame(pos=(10, 10), size=(100, 100)) - print(f"Frame created: visible={frame.visible}, opacity={frame.opacity}") - - bounds = frame.get_bounds() - print(f"Bounds: {bounds}") - - frame.move(5, 5) - print("Move completed") - - frame.resize(150, 150) - print("Resize completed") - - print("PASS - No crash!") - except Exception as e: - print(f"ERROR: {e}") - - sys.exit(0) - +# Initialize scene test = mcrfpy.Scene("test") -simple_test_timer = mcrfpy.Timer("simple_test", simple_test, 100, once=True) \ No newline at end of file +test.activate() +mcrfpy.step(0.01) + +try: + # Test basic functionality + frame = mcrfpy.Frame(pos=(10, 10), size=(100, 100)) + print(f"Frame created: visible={frame.visible}, opacity={frame.opacity}") + + bounds = frame.get_bounds() + print(f"Bounds: {bounds}") + + frame.move(5, 5) + print("Move completed") + + frame.resize(150, 150) + print("Resize completed") + + print("PASS - No crash!") + sys.exit(0) +except Exception as e: + print(f"ERROR: {e}") + print("FAIL") + sys.exit(1)