McRogueFace/tests/unit/test_scene_object_api.py
John McCardle b863698f6e test: Add comprehensive Scene object API test
Demonstrates the object-oriented Scene API as alternative to module-level
functions. Key features tested:

- Scene object creation and properties (name, active, children)
- scene.activate() vs mcrfpy.setScene()
- scene.on_key property - can be set on ANY scene, not just current
- Scene visual properties (pos, visible, opacity)
- Subclassing for lifecycle callbacks (on_enter, on_exit, update)

The on_key advantage resolves confusion with keypressScene() which only
works on the currently active scene.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 19:48:00 -05:00

235 lines
8.1 KiB
Python

#!/usr/bin/env python3
"""Test the object-oriented Scene API (alternative to module-level functions).
The Scene object provides an OOP approach to scene management with several advantages:
1. `scene.on_key` can be set on ANY scene, not just the current one
2. `scene.children` provides direct access to UI elements
3. Subclassing enables lifecycle callbacks (on_enter, on_exit, update, etc.)
This is the recommended approach for new code, replacing:
- mcrfpy.createScene(name) -> scene = mcrfpy.Scene(name)
- mcrfpy.setScene(name) -> scene.activate()
- mcrfpy.sceneUI(name) -> scene.children
- mcrfpy.keypressScene(callback) -> scene.on_key = callback
"""
import mcrfpy
import sys
def test_scene_object_basics():
"""Test basic Scene object creation and properties."""
print("=== Test: Scene Object Basics ===")
# Create scene using object-oriented approach
scene = mcrfpy.Scene("oop_test")
# Check name property
assert scene.name == "oop_test", f"Expected 'oop_test', got '{scene.name}'"
print(f" name: {scene.name}")
# Check active property (should be False, not yet activated)
print(f" active: {scene.active}")
# Check children property returns UICollection
children = scene.children
print(f" children type: {type(children).__name__}")
print(f" children count: {len(children)}")
# Add UI elements via children
frame = mcrfpy.Frame(pos=(50, 50), size=(200, 100), fill_color=mcrfpy.Color(100, 100, 200))
scene.children.append(frame)
print(f" children count after append: {len(scene.children)}")
print(" PASS: Basic properties work correctly")
return scene
def test_scene_activation():
"""Test scene activation."""
print("\n=== Test: Scene Activation ===")
scene1 = mcrfpy.Scene("scene_a")
scene2 = mcrfpy.Scene("scene_b")
# Neither active yet
print(f" Before activation - scene1.active: {scene1.active}, scene2.active: {scene2.active}")
# Activate scene1
scene1.activate()
print(f" After scene1.activate() - scene1.active: {scene1.active}, scene2.active: {scene2.active}")
assert scene1.active == True, "scene1 should be active"
assert scene2.active == False, "scene2 should not be active"
# Activate scene2
scene2.activate()
print(f" After scene2.activate() - scene1.active: {scene1.active}, scene2.active: {scene2.active}")
assert scene1.active == False, "scene1 should not be active now"
assert scene2.active == True, "scene2 should be active"
print(" PASS: Scene activation works correctly")
def test_scene_on_key():
"""Test setting on_key callback on scene objects.
This is the KEY ADVANTAGE over module-level keypressScene():
You can set on_key on ANY scene, not just the current one!
"""
print("\n=== Test: Scene on_key Property ===")
scene1 = mcrfpy.Scene("keys_scene1")
scene2 = mcrfpy.Scene("keys_scene2")
# Track which callback was called
callback_log = []
def scene1_keyhandler(key, action):
callback_log.append(("scene1", key, action))
def scene2_keyhandler(key, action):
callback_log.append(("scene2", key, action))
# Set callbacks on BOTH scenes BEFORE activating either
# This is impossible with keypressScene() which only works on current scene!
scene1.on_key = scene1_keyhandler
scene2.on_key = scene2_keyhandler
print(f" scene1.on_key set: {scene1.on_key is not None}")
print(f" scene2.on_key set: {scene2.on_key is not None}")
# Verify callbacks are retrievable
assert callable(scene1.on_key), "scene1.on_key should be callable"
assert callable(scene2.on_key), "scene2.on_key should be callable"
# Test clearing callback
scene1.on_key = None
assert scene1.on_key is None, "scene1.on_key should be None after clearing"
print(" scene1.on_key cleared successfully")
# Re-set it
scene1.on_key = scene1_keyhandler
print(" PASS: on_key property works correctly")
def test_scene_visual_properties():
"""Test scene-level visual properties (pos, visible, opacity)."""
print("\n=== Test: Scene Visual Properties ===")
scene = mcrfpy.Scene("visual_props_test")
# Test pos property
print(f" Initial pos: {scene.pos}")
scene.pos = (100, 50)
print(f" After setting pos=(100, 50): {scene.pos}")
# Test visible property
print(f" Initial visible: {scene.visible}")
scene.visible = False
print(f" After setting visible=False: {scene.visible}")
assert scene.visible == False, "visible should be False"
scene.visible = True
# Test opacity property
print(f" Initial opacity: {scene.opacity}")
scene.opacity = 0.5
print(f" After setting opacity=0.5: {scene.opacity}")
assert 0.49 < scene.opacity < 0.51, f"opacity should be ~0.5, got {scene.opacity}"
print(" PASS: Visual properties work correctly")
def test_scene_subclass():
"""Test subclassing Scene for lifecycle callbacks."""
print("\n=== Test: Scene Subclass with Lifecycle ===")
class GameScene(mcrfpy.Scene):
def __init__(self, name):
super().__init__(name)
self.enter_count = 0
self.exit_count = 0
self.update_count = 0
def on_enter(self):
self.enter_count += 1
print(f" GameScene.on_enter() called (count: {self.enter_count})")
def on_exit(self):
self.exit_count += 1
print(f" GameScene.on_exit() called (count: {self.exit_count})")
def on_keypress(self, key, action):
print(f" GameScene.on_keypress({key}, {action})")
def update(self, dt):
self.update_count += 1
# Note: update is called every frame, so we don't print
game_scene = GameScene("game_scene_test")
other_scene = mcrfpy.Scene("other_scene_test")
# Add some UI to game scene
game_scene.children.append(
mcrfpy.Caption(pos=(100, 100), text="Game Scene", fill_color=mcrfpy.Color(255, 255, 255))
)
print(f" Created GameScene with {len(game_scene.children)} children")
print(f" enter_count before activation: {game_scene.enter_count}")
# Activate - should trigger on_enter
game_scene.activate()
print(f" enter_count after activation: {game_scene.enter_count}")
# Switch away - should trigger on_exit
other_scene.activate()
print(f" exit_count after switching: {game_scene.exit_count}")
print(" PASS: Subclassing works correctly")
def test_comparison_with_module_functions():
"""Demonstrate the difference between old and new approaches."""
print("\n=== Comparison: Module Functions vs Scene Objects ===")
print("\n OLD APPROACH (module-level functions):")
print(" mcrfpy.createScene('my_scene')")
print(" mcrfpy.setScene('my_scene')")
print(" ui = mcrfpy.sceneUI('my_scene')")
print(" ui.append(mcrfpy.Frame(...))")
print(" mcrfpy.keypressScene(handler) # ONLY works on current scene!")
print("\n NEW APPROACH (Scene objects):")
print(" scene = mcrfpy.Scene('my_scene')")
print(" scene.activate()")
print(" scene.children.append(mcrfpy.Frame(...))")
print(" scene.on_key = handler # Works on ANY scene!")
print("\n KEY BENEFITS:")
print(" 1. scene.on_key can be set on non-active scenes")
print(" 2. Subclassing enables on_enter/on_exit/update callbacks")
print(" 3. Object reference makes code more readable")
print(" 4. scene.children is clearer than sceneUI(name)")
print("\n PASS: Documentation complete")
def main():
"""Run all Scene object API tests."""
print("=" * 60)
print("Scene Object API Test Suite")
print("=" * 60)
try:
test_scene_object_basics()
test_scene_activation()
test_scene_on_key()
test_scene_visual_properties()
test_scene_subclass()
test_comparison_with_module_functions()
print("\n" + "=" * 60)
print("ALL TESTS PASSED!")
print("=" * 60)
sys.exit(0)
except Exception as e:
print(f"\nTEST FAILED: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()