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>
This commit is contained in:
parent
9f481a2e4a
commit
b863698f6e
1 changed files with 235 additions and 0 deletions
235
tests/unit/test_scene_object_api.py
Normal file
235
tests/unit/test_scene_object_api.py
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
#!/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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue