tests (alleged)

This commit is contained in:
Yonokid
2025-12-27 18:30:58 -05:00
parent fbcd181667
commit 7afb1da1cd
11 changed files with 2956 additions and 4 deletions

695
test/libs/test_animation.py Normal file
View File

@@ -0,0 +1,695 @@
import unittest
from unittest.mock import patch
from libs.animation import (
Animation,
BaseAnimation,
FadeAnimation,
MoveAnimation,
TextStretchAnimation,
TextureChangeAnimation,
TextureResizeAnimation,
parse_animations,
)
class TestBaseAnimation(unittest.TestCase):
"""Test cases for the BaseAnimation class."""
@patch('libs.animation.get_current_ms')
@patch('libs.animation.global_data')
def setUp(self, mock_global_data, mock_get_ms):
"""Set up test fixtures."""
mock_get_ms.return_value = 0.0
mock_global_data.input_locked = 0
@patch('libs.animation.get_current_ms')
def test_initialization(self, mock_get_ms):
"""Test basic initialization of BaseAnimation."""
mock_get_ms.return_value = 100.0
anim = BaseAnimation(duration=1000.0, delay=100.0, loop=True, lock_input=True)
self.assertEqual(anim.duration, 1000.0)
self.assertEqual(anim.delay, 100.0)
self.assertEqual(anim.delay_saved, 100.0)
self.assertEqual(anim.start_ms, 100.0)
self.assertFalse(anim.is_finished)
self.assertEqual(anim.attribute, 0)
self.assertFalse(anim.is_started)
self.assertTrue(anim.loop)
self.assertTrue(anim.lock_input)
@patch('libs.animation.get_current_ms')
@patch('libs.animation.global_data')
def test_start(self, mock_global_data, mock_get_ms):
"""Test starting an animation."""
mock_get_ms.return_value = 200.0
mock_global_data.input_locked = 0
anim = BaseAnimation(duration=1000.0, lock_input=True)
anim.start()
self.assertTrue(anim.is_started)
self.assertFalse(anim.is_finished)
self.assertEqual(mock_global_data.input_locked, 1)
@patch('libs.animation.get_current_ms')
@patch('libs.animation.global_data')
def test_restart(self, mock_global_data, mock_get_ms):
"""Test restarting an animation."""
mock_get_ms.side_effect = [0.0, 500.0, 1000.0]
mock_global_data.input_locked = 0
anim = BaseAnimation(duration=1000.0, delay=100.0, lock_input=True)
anim.is_finished = True
anim.delay = 0.0
anim.restart()
self.assertEqual(anim.start_ms, 500.0)
self.assertFalse(anim.is_finished)
self.assertEqual(anim.delay, 100.0)
self.assertEqual(mock_global_data.input_locked, 1)
@patch('libs.animation.get_current_ms')
@patch('libs.animation.global_data')
def test_pause_unpause(self, mock_global_data, mock_get_ms):
"""Test pausing and unpausing."""
mock_get_ms.return_value = 0.0
mock_global_data.input_locked = 1
anim = BaseAnimation(duration=1000.0, lock_input=True)
anim.is_started = True
anim.pause()
self.assertFalse(anim.is_started)
self.assertEqual(mock_global_data.input_locked, 0)
anim.unpause()
self.assertTrue(anim.is_started)
self.assertEqual(mock_global_data.input_locked, 1)
@patch('libs.animation.get_current_ms')
def test_loop_restarts(self, mock_get_ms):
"""Test that looped animations restart when finished."""
mock_get_ms.side_effect = [0.0, 100.0]
anim = BaseAnimation(duration=1000.0, loop=True)
anim.is_finished = True
with patch.object(anim, 'restart') as mock_restart:
anim.update(100.0)
mock_restart.assert_called_once()
@patch('libs.animation.get_current_ms')
@patch('libs.animation.global_data')
def test_input_lock_unlock(self, mock_global_data, mock_get_ms):
"""Test input locking mechanism."""
mock_get_ms.return_value = 0.0
mock_global_data.input_locked = 1
anim = BaseAnimation(duration=1000.0, lock_input=True)
anim.is_finished = True
anim.unlocked = False
anim.update(100.0)
self.assertTrue(anim.unlocked)
self.assertEqual(mock_global_data.input_locked, 0)
def test_easing_functions(self):
"""Test easing functions produce expected values."""
anim = BaseAnimation(duration=1000.0)
# Test quadratic ease in
self.assertAlmostEqual(anim._ease_in(0.5, "quadratic"), 0.25)
self.assertAlmostEqual(anim._ease_in(1.0, "quadratic"), 1.0)
# Test cubic ease in
self.assertAlmostEqual(anim._ease_in(0.5, "cubic"), 0.125)
# Test exponential ease in
self.assertEqual(anim._ease_in(0.0, "exponential"), 0)
# Test quadratic ease out
self.assertAlmostEqual(anim._ease_out(0.5, "quadratic"), 0.75)
# Test cubic ease out
self.assertAlmostEqual(anim._ease_out(0.5, "cubic"), 0.875)
# Test exponential ease out
self.assertEqual(anim._ease_out(1.0, "exponential"), 1)
class TestFadeAnimation(unittest.TestCase):
"""Test cases for the FadeAnimation class."""
@patch('libs.animation.get_current_ms')
def test_initialization(self, mock_get_ms):
"""Test fade animation initialization."""
mock_get_ms.return_value = 0.0
anim = FadeAnimation(
duration=1000.0,
initial_opacity=1.0,
final_opacity=0.0,
delay=100.0,
ease_in="quadratic"
)
self.assertEqual(anim.initial_opacity, 1.0)
self.assertEqual(anim.final_opacity, 0.0)
self.assertEqual(anim.attribute, 1.0)
self.assertEqual(anim.ease_in, "quadratic")
@patch('libs.animation.get_current_ms')
def test_fade_during_delay(self, mock_get_ms):
"""Test that opacity stays at initial during delay."""
mock_get_ms.return_value = 0.0
anim = FadeAnimation(duration=1000.0, initial_opacity=1.0, final_opacity=0.0, delay=500.0)
anim.start()
anim.update(250.0) # Within delay period
self.assertEqual(anim.attribute, 1.0)
self.assertFalse(anim.is_finished)
@patch('libs.animation.get_current_ms')
def test_fade_progression(self, mock_get_ms):
"""Test fade progresses correctly."""
mock_get_ms.return_value = 0.0
anim = FadeAnimation(duration=1000.0, initial_opacity=1.0, final_opacity=0.0)
anim.start()
anim.update(500.0) # Halfway through
self.assertAlmostEqual(anim.attribute, 0.5, places=2)
self.assertFalse(anim.is_finished)
@patch('libs.animation.get_current_ms')
def test_fade_completion(self, mock_get_ms):
"""Test fade completes at final opacity."""
mock_get_ms.return_value = 0.0
anim = FadeAnimation(duration=1000.0, initial_opacity=1.0, final_opacity=0.0)
anim.start()
anim.update(1000.0) # End of animation
self.assertEqual(anim.attribute, 0.0)
self.assertTrue(anim.is_finished)
@patch('libs.animation.get_current_ms')
def test_fade_with_reverse_delay(self, mock_get_ms):
"""Test fade reverses after reverse_delay."""
mock_get_ms.return_value = 0.0
anim = FadeAnimation(
duration=1000.0,
initial_opacity=1.0,
final_opacity=0.0,
reverse_delay=200.0
)
anim.start()
anim.update(1000.0) # Complete first fade
self.assertEqual(anim.attribute, 0.0)
self.assertTrue(anim.is_reversing)
self.assertEqual(anim.initial_opacity, 0.0)
self.assertEqual(anim.final_opacity, 1.0)
self.assertFalse(anim.is_finished)
@patch('libs.animation.get_current_ms')
def test_fade_with_easing(self, mock_get_ms):
"""Test fade applies easing correctly."""
mock_get_ms.return_value = 0.0
anim = FadeAnimation(
duration=1000.0,
initial_opacity=0.0,
final_opacity=1.0,
ease_in="quadratic"
)
anim.start()
anim.update(500.0) # Halfway
# With quadratic ease in, at 0.5 progress we should have 0.25
self.assertAlmostEqual(anim.attribute, 0.25, places=2)
class TestMoveAnimation(unittest.TestCase):
"""Test cases for the MoveAnimation class."""
@patch('libs.animation.get_current_ms')
def test_initialization(self, mock_get_ms):
"""Test move animation initialization."""
mock_get_ms.return_value = 0.0
anim = MoveAnimation(
duration=1000.0,
start_position=0,
total_distance=100,
delay=50.0
)
self.assertEqual(anim.start_position, 0)
self.assertEqual(anim.total_distance, 100)
self.assertEqual(anim.delay, 50.0)
@patch('libs.animation.get_current_ms')
def test_move_during_delay(self, mock_get_ms):
"""Test position stays at start during delay."""
mock_get_ms.return_value = 0.0
anim = MoveAnimation(duration=1000.0, start_position=50, total_distance=100, delay=200.0)
anim.start()
anim.update(100.0) # Within delay
self.assertEqual(anim.attribute, 50)
@patch('libs.animation.get_current_ms')
def test_move_progression(self, mock_get_ms):
"""Test move progresses correctly."""
mock_get_ms.return_value = 0.0
anim = MoveAnimation(duration=1000.0, start_position=0, total_distance=100)
anim.start()
anim.update(500.0) # Halfway
self.assertAlmostEqual(anim.attribute, 50.0, places=2)
@patch('libs.animation.get_current_ms')
def test_move_completion(self, mock_get_ms):
"""Test move completes at final position."""
mock_get_ms.return_value = 0.0
anim = MoveAnimation(duration=1000.0, start_position=0, total_distance=100)
anim.start()
anim.update(1000.0)
self.assertEqual(anim.attribute, 100)
self.assertTrue(anim.is_finished)
@patch('libs.animation.get_current_ms')
def test_move_with_reverse_delay(self, mock_get_ms):
"""Test move reverses after reverse_delay."""
mock_get_ms.return_value = 0.0
anim = MoveAnimation(
duration=1000.0,
start_position=0,
total_distance=100,
reverse_delay=100.0
)
anim.start()
anim.update(1000.0) # Complete first move
self.assertEqual(anim.start_position, 100)
self.assertEqual(anim.total_distance, -100)
self.assertIsNone(anim.reverse_delay)
self.assertFalse(anim.is_finished)
@patch('libs.animation.get_current_ms')
def test_move_with_easing(self, mock_get_ms):
"""Test move applies easing."""
mock_get_ms.return_value = 0.0
anim = MoveAnimation(
duration=1000.0,
start_position=0,
total_distance=100,
ease_out="quadratic"
)
anim.start()
anim.update(500.0)
# With quadratic ease out, at 0.5 progress we should have 0.75
self.assertAlmostEqual(anim.attribute, 75.0, places=2)
class TestTextureChangeAnimation(unittest.TestCase):
"""Test cases for the TextureChangeAnimation class."""
@patch('libs.animation.get_current_ms')
def test_initialization(self, mock_get_ms):
"""Test texture change animation initialization."""
mock_get_ms.return_value = 0.0
textures = [(0.0, 100.0, 0), (100.0, 200.0, 1), (200.0, 300.0, 2)]
anim = TextureChangeAnimation(duration=300.0, textures=textures)
self.assertEqual(anim.textures, textures)
self.assertEqual(anim.attribute, 0) # First texture index
@patch('libs.animation.get_current_ms')
def test_texture_change_progression(self, mock_get_ms):
"""Test texture changes at correct times."""
mock_get_ms.return_value = 0.0
textures = [(0.0, 100.0, 0), (100.0, 200.0, 1), (200.0, 300.0, 2)]
anim = TextureChangeAnimation(duration=300.0, textures=textures)
anim.start()
anim.update(50.0)
self.assertEqual(anim.attribute, 0)
anim.update(150.0)
self.assertEqual(anim.attribute, 1)
anim.update(250.0)
self.assertEqual(anim.attribute, 2)
@patch('libs.animation.get_current_ms')
def test_texture_change_completion(self, mock_get_ms):
"""Test texture change completes."""
mock_get_ms.return_value = 0.0
textures = [(0.0, 100.0, 0), (100.0, 200.0, 1)]
anim = TextureChangeAnimation(duration=200.0, textures=textures)
anim.start()
anim.update(300.0) # Past duration
self.assertTrue(anim.is_finished)
@patch('libs.animation.get_current_ms')
def test_texture_change_with_delay(self, mock_get_ms):
"""Test texture change respects delay."""
mock_get_ms.return_value = 0.0
textures = [(0.0, 100.0, 0), (100.0, 200.0, 1)]
anim = TextureChangeAnimation(duration=200.0, textures=textures, delay=100.0)
anim.start()
anim.update(50.0) # During delay
self.assertEqual(anim.attribute, 0)
anim.update(150.0) # 50ms into animation (after delay)
self.assertEqual(anim.attribute, 0)
class TestTextureResizeAnimation(unittest.TestCase):
"""Test cases for the TextureResizeAnimation class."""
@patch('libs.animation.get_current_ms')
def test_initialization(self, mock_get_ms):
"""Test texture resize initialization."""
mock_get_ms.return_value = 0.0
anim = TextureResizeAnimation(
duration=1000.0,
initial_size=1.0,
final_size=2.0
)
self.assertEqual(anim.initial_size, 1.0)
self.assertEqual(anim.final_size, 2.0)
self.assertEqual(anim.attribute, 1.0)
@patch('libs.animation.get_current_ms')
def test_resize_progression(self, mock_get_ms):
"""Test resize progresses correctly."""
mock_get_ms.return_value = 0.0
anim = TextureResizeAnimation(duration=1000.0, initial_size=1.0, final_size=2.0)
anim.start()
anim.update(500.0) # Halfway
self.assertAlmostEqual(anim.attribute, 1.5, places=2)
@patch('libs.animation.get_current_ms')
def test_resize_completion(self, mock_get_ms):
"""Test resize completes."""
mock_get_ms.return_value = 0.0
anim = TextureResizeAnimation(duration=1000.0, initial_size=1.0, final_size=0.5)
anim.start()
anim.update(1000.0)
self.assertEqual(anim.attribute, 0.5)
self.assertTrue(anim.is_finished)
class TestTextStretchAnimation(unittest.TestCase):
"""Test cases for the TextStretchAnimation class."""
@patch('libs.animation.get_current_ms')
def test_stretch_phases(self, mock_get_ms):
"""Test text stretch animation phases."""
mock_get_ms.return_value = 0.0
anim = TextStretchAnimation(duration=100.0)
anim.start()
# Phase 1: Growing
anim.update(50.0)
self.assertGreater(anim.attribute, 2)
# Phase 2: Shrinking back
anim.update(150.0)
self.assertGreater(anim.attribute, 0)
# Phase 3: Finished
anim.update(300.0)
self.assertEqual(anim.attribute, 0)
self.assertTrue(anim.is_finished)
class TestAnimationFactory(unittest.TestCase):
"""Test cases for the Animation factory class."""
def test_create_fade(self):
"""Test factory creates fade animation."""
anim = Animation.create_fade(1000.0, initial_opacity=1.0, final_opacity=0.0)
self.assertIsInstance(anim, FadeAnimation)
self.assertEqual(anim.duration, 1000.0)
self.assertEqual(anim.initial_opacity, 1.0)
def test_create_move(self):
"""Test factory creates move animation."""
anim = Animation.create_move(1000.0, start_position=0, total_distance=100)
self.assertIsInstance(anim, MoveAnimation)
self.assertEqual(anim.duration, 1000.0)
self.assertEqual(anim.total_distance, 100)
def test_create_texture_change(self):
"""Test factory creates texture change animation."""
textures = [(0.0, 100.0, 0)]
anim = Animation.create_texture_change(1000.0, textures=textures)
self.assertIsInstance(anim, TextureChangeAnimation)
self.assertEqual(anim.textures, textures)
def test_create_texture_resize(self):
"""Test factory creates texture resize animation."""
anim = Animation.create_texture_resize(1000.0, initial_size=1.0, final_size=2.0)
self.assertIsInstance(anim, TextureResizeAnimation)
self.assertEqual(anim.initial_size, 1.0)
class TestParseAnimations(unittest.TestCase):
"""Test cases for parse_animations function."""
def test_parse_basic_animation(self):
"""Test parsing a simple animation."""
animation_json = [
{
"id": 1,
"type": "fade",
"duration": 1000.0,
"initial_opacity": 1.0,
"final_opacity": 0.0
}
]
result = parse_animations(animation_json)
self.assertIn(1, result)
self.assertIsInstance(result[1], FadeAnimation)
self.assertEqual(result[1].duration, 1000.0)
def test_parse_multiple_animations(self):
"""Test parsing multiple animations."""
animation_json = [
{"id": 1, "type": "fade", "duration": 1000.0},
{"id": 2, "type": "move", "duration": 500.0, "total_distance": 50}
]
result = parse_animations(animation_json)
self.assertEqual(len(result), 2)
self.assertIsInstance(result[1], FadeAnimation)
self.assertIsInstance(result[2], MoveAnimation)
def test_parse_with_reference(self):
"""Test parsing animations with references."""
animation_json = [
{"id": 1, "type": "fade", "duration": 1000.0, "initial_opacity": 1.0},
{
"id": 2,
"type": "fade",
"duration": {"reference_id": 1, "property": "duration"},
"initial_opacity": 0.5
}
]
result = parse_animations(animation_json)
self.assertEqual(result[2].duration, 1000.0)
self.assertEqual(result[2].initial_opacity, 0.5)
def test_parse_with_reference_and_init_val(self):
"""Test parsing with reference and init_val modifier."""
animation_json = [
{"id": 1, "type": "fade", "duration": 1000.0},
{
"id": 2,
"type": "fade",
"duration": {
"reference_id": 1,
"property": "duration",
"init_val": 500.0
}
}
]
result = parse_animations(animation_json)
self.assertEqual(result[2].duration, 1500.0)
def test_parse_missing_id_raises_error(self):
"""Test that missing id raises exception."""
animation_json = [
{"type": "fade", "duration": 1000.0}
]
with self.assertRaises(Exception) as context:
parse_animations(animation_json)
self.assertIn("requires id", str(context.exception))
def test_parse_missing_type_raises_error(self):
"""Test that missing type raises exception."""
animation_json = [
{"id": 1, "duration": 1000.0}
]
with self.assertRaises(Exception) as context:
parse_animations(animation_json)
self.assertIn("requires type", str(context.exception))
def test_parse_circular_reference_raises_error(self):
"""Test that circular references are detected."""
animation_json = [
{
"id": 1,
"type": "fade",
"duration": {"reference_id": 2, "property": "duration"}
},
{
"id": 2,
"type": "fade",
"duration": {"reference_id": 1, "property": "duration"}
}
]
with self.assertRaises(Exception) as context:
parse_animations(animation_json)
self.assertIn("Circular reference", str(context.exception))
def test_parse_unknown_type_raises_error(self):
"""Test that unknown animation type raises exception."""
animation_json = [
{"id": 1, "type": "unknown_type", "duration": 1000.0}
]
with self.assertRaises(Exception) as context:
parse_animations(animation_json)
self.assertIn("Unknown Animation type", str(context.exception))
def test_parse_missing_reference_property_raises_error(self):
"""Test that missing reference property raises exception."""
animation_json = [
{"id": 1, "type": "fade", "duration": 1000.0},
{
"id": 2,
"type": "fade",
"duration": {"reference_id": 1}
}
]
with self.assertRaises(Exception) as context:
parse_animations(animation_json)
self.assertIn("requires 'property'", str(context.exception))
def test_parse_nonexistent_reference_raises_error(self):
"""Test that referencing nonexistent animation raises exception."""
animation_json = [
{
"id": 1,
"type": "fade",
"duration": {"reference_id": 999, "property": "duration"}
}
]
with self.assertRaises(Exception) as context:
parse_animations(animation_json)
self.assertIn("not found", str(context.exception))
def test_parse_ignores_comments(self):
"""Test that comments are ignored during parsing."""
animation_json = [
{
"id": 1,
"type": "fade",
"duration": 1000.0,
"comment": "This is a fade animation"
}
]
result = parse_animations(animation_json)
self.assertIn(1, result)
self.assertIsInstance(result[1], FadeAnimation)
def test_parse_nested_references(self):
"""Test parsing nested reference chains."""
animation_json = [
{"id": 1, "type": "fade", "duration": 1000.0},
{
"id": 2,
"type": "fade",
"duration": {"reference_id": 1, "property": "duration"}
},
{
"id": 3,
"type": "fade",
"duration": {
"reference_id": 2,
"property": "duration",
"init_val": 500.0
}
}
]
result = parse_animations(animation_json)
self.assertEqual(result[3].duration, 1500.0)
if __name__ == '__main__':
unittest.main()

518
test/libs/test_audio.py Normal file
View File

@@ -0,0 +1,518 @@
import shutil
import struct
import unittest
import wave
from pathlib import Path
from unittest.mock import patch
from libs.audio import AudioEngine, audio
from libs.config import VolumeConfig
DEFAULT_CONFIG = VolumeConfig(sound=0.8, music=0.7, voice=0.6, hitsound=0.5, attract_mode=0.4)
class TestAudioEngine(unittest.TestCase):
"""Integration tests using the audio library."""
@classmethod
def setUpClass(cls):
"""Set up test fixtures once for all tests."""
# Create temporary directory for test audio files
cls.test_dir = Path().cwd() / Path("temp")
cls.test_dir.mkdir(exist_ok=True)
cls.sounds_dir = Path(cls.test_dir) / "Sounds"
cls.sounds_dir.mkdir(exist_ok=True)
# Create test WAV files
cls._create_test_wav(cls.sounds_dir / "don.wav")
cls._create_test_wav(cls.sounds_dir / "ka.wav")
cls._create_test_wav(cls.sounds_dir / "test_sound.wav")
cls._create_test_wav(cls.sounds_dir / "test_music.wav", duration=2.0)
# Create screen sounds directory
cls.screen_sounds = cls.sounds_dir / "menu"
cls.screen_sounds.mkdir()
cls._create_test_wav(cls.screen_sounds / "click.wav")
cls._create_test_wav(cls.screen_sounds / "hover.wav")
# Create global sounds directory
cls.global_sounds = cls.sounds_dir / "global"
cls.global_sounds.mkdir()
cls._create_test_wav(cls.global_sounds / "confirm.wav")
cls.volume_presets = DEFAULT_CONFIG
@classmethod
def tearDownClass(cls):
"""Clean up test files."""
shutil.rmtree(cls.test_dir)
@staticmethod
def _create_test_wav(filepath, duration=0.1, frequency=440):
"""Create a simple test WAV file."""
sample_rate = 44100
num_samples = int(sample_rate * duration)
with wave.open(str(filepath), 'w') as wav_file:
wav_file.setnchannels(1) # Mono
wav_file.setsampwidth(2) # 16-bit
wav_file.setframerate(sample_rate)
for i in range(num_samples):
# Generate a simple sine wave
value = int(32767.0 * 0.3 *
(i % (sample_rate // frequency)) /
(sample_rate // frequency))
wav_file.writeframes(struct.pack('h', value))
def setUp(self):
"""Set up each test."""
self.mock_config_path = self.sounds_dir
# Store original audio singleton state to avoid test pollution
self._original_audio_sounds_path = audio.sounds_path
def tearDown(self):
"""Tear down each test."""
# Restore original audio singleton state
audio.sounds_path = self._original_audio_sounds_path
# Clear any sounds or music loaded during tests
if hasattr(audio, 'sounds') and isinstance(audio.sounds, dict):
audio.sounds.clear()
if hasattr(audio, 'music_streams') and isinstance(audio.music_streams, dict):
audio.music_streams.clear()
@patch('libs.audio.get_config')
def test_initialization(self, mock_config):
"""Test AudioEngine initialization."""
mock_config.return_value = {"paths": {"skin": f"{self.sounds_dir}"}}
engine = AudioEngine(
device_type=0,
sample_rate=44100.0,
buffer_size=512,
volume_presets=self.volume_presets,
sounds_path=self.sounds_dir
)
self.assertEqual(engine.device_type, 0)
self.assertEqual(engine.target_sample_rate, 44100.0)
self.assertEqual(engine.buffer_size, 512)
self.assertEqual(engine.volume_presets, self.volume_presets)
self.assertFalse(engine.audio_device_ready)
@patch('libs.audio.get_config')
def test_init_and_close_audio_device(self, mock_config):
"""Test initializing and closing audio device."""
mock_config.return_value = {"paths": {"skin": f"{self.sounds_dir}"}}
engine = AudioEngine(0, 44100.0, 512, self.volume_presets, sounds_path=self.sounds_dir)
engine.set_log_level(10)
# Initialize
success = engine.init_audio_device()
self.assertTrue(success)
self.assertTrue(engine.audio_device_ready)
self.assertTrue(engine.is_audio_device_ready())
# Close
engine.close_audio_device()
self.assertFalse(engine.audio_device_ready)
@patch('libs.audio.get_config')
def test_master_volume(self, mock_config):
"""Test master volume control."""
mock_config.return_value = {"paths": {"skin": f"{self.sounds_dir}"}}
engine = AudioEngine(0, 44100.0, 512, self.volume_presets, sounds_path=self.sounds_dir)
engine.set_log_level(10)
if engine.init_audio_device():
try:
# Set and get master volume
engine.set_master_volume(0.75)
volume = engine.get_master_volume()
self.assertAlmostEqual(volume, 0.75, places=2)
# Test clamping
engine.set_master_volume(1.5)
volume = engine.get_master_volume()
self.assertLessEqual(volume, 1.0)
engine.set_master_volume(-0.5)
volume = engine.get_master_volume()
self.assertGreaterEqual(volume, 0.0)
finally:
engine.close_audio_device()
@patch('libs.audio.get_config')
def test_load_and_unload_sound(self, mock_config):
"""Test loading and unloading sounds."""
mock_config.return_value = {"paths": {"skin": f"{self.sounds_dir}"}}
engine = AudioEngine(0, 44100.0, 512, self.volume_presets, sounds_path=self.sounds_dir)
engine.set_log_level(10)
if engine.init_audio_device():
try:
# Load sound
sound_path = self.sounds_dir / "test_sound.wav"
sound_id = engine.load_sound(sound_path, "test")
self.assertEqual(sound_id, "test")
self.assertIn("test", engine.sounds)
# Unload sound
engine.unload_sound("test")
self.assertNotIn("test", engine.sounds)
finally:
engine.close_audio_device()
@patch('libs.audio.get_config')
def test_load_nonexistent_sound(self, mock_config):
"""Test loading a non-existent sound file."""
mock_config.return_value = {"paths": {"skin": f"{self.sounds_dir}"}}
engine = AudioEngine(0, 44100.0, 512, self.volume_presets, sounds_path=self.sounds_dir)
engine.set_log_level(10)
if engine.init_audio_device():
try:
sound_id = engine.load_sound(Path("nonexistent.wav"), "bad")
self.assertEqual(sound_id, "")
self.assertNotIn("bad", engine.sounds)
finally:
engine.close_audio_device()
@patch('libs.audio.get_config')
def test_play_and_stop_sound(self, mock_config):
"""Test playing and stopping sounds."""
mock_config.return_value = {"paths": {"skin": f"{self.sounds_dir}"}}
engine = AudioEngine(0, 44100.0, 512, self.volume_presets, sounds_path=self.sounds_dir)
engine.set_log_level(10)
if engine.init_audio_device():
try:
# Load and play sound
sound_path = self.sounds_dir / "test_sound.wav"
engine.load_sound(sound_path, "test")
engine.play_sound("test", "sound")
# Give it a moment to start
import time
time.sleep(0.05)
# Check if playing (might not be if audio is very short)
# Just verify no exceptions were raised
is_playing = engine.is_sound_playing("test")
self.assertIsInstance(is_playing, bool)
# Stop sound
engine.stop_sound("test")
finally:
engine.close_audio_device()
@patch('libs.audio.get_config')
def test_play_don_and_kat(self, mock_config):
"""Test playing the special don and kat sounds."""
mock_config.return_value = {"paths": {"skin": f"{self.sounds_dir}"}}
engine = AudioEngine(0, 44100.0, 512, self.volume_presets, sounds_path=self.sounds_dir)
engine.set_log_level(10)
if engine.init_audio_device():
try:
# Play don
engine.play_sound("don", "sound")
is_playing = engine.is_sound_playing("don")
self.assertIsInstance(is_playing, bool)
engine.stop_sound("don")
# Play kat
engine.play_sound("kat", "sound")
is_playing = engine.is_sound_playing("kat")
self.assertIsInstance(is_playing, bool)
engine.stop_sound("kat")
finally:
engine.close_audio_device()
@patch('libs.audio.get_config')
def test_sound_volume_control(self, mock_config):
"""Test setting sound volume."""
mock_config.return_value = {"paths": {"skin": f"{self.sounds_dir}"}}
engine = AudioEngine(0, 44100.0, 512, self.volume_presets, sounds_path=self.sounds_dir)
engine.set_log_level(10)
if engine.init_audio_device():
try:
sound_path = self.sounds_dir / "test_sound.wav"
engine.load_sound(sound_path, "test")
# Set volume (should not raise exception)
engine.set_sound_volume("test", 0.5)
engine.play_sound("test", "")
finally:
engine.close_audio_device()
@patch('libs.audio.get_config')
def test_sound_pan_control(self, mock_config):
"""Test setting sound pan."""
mock_config.return_value = {"paths": {"skin": f"{self.sounds_dir}"}}
engine = AudioEngine(0, 44100.0, 512, self.volume_presets, sounds_path=self.sounds_dir)
engine.set_log_level(10)
if engine.init_audio_device():
try:
sound_path = self.sounds_dir / "test_sound.wav"
engine.load_sound(sound_path, "test")
# Set pan (should not raise exception)
engine.set_sound_pan("test", -0.5) # Left
engine.set_sound_pan("test", 0.5) # Right
engine.set_sound_pan("test", 0.0) # Center
finally:
engine.close_audio_device()
@patch('libs.audio.get_config')
def test_load_screen_sounds(self, mock_config):
"""Test loading sounds for a screen."""
mock_config.return_value = {"paths": {"skin": f"{self.sounds_dir}"}}
engine = AudioEngine(0, 44100.0, 512, self.volume_presets, sounds_path=self.sounds_dir)
engine.set_log_level(10)
if engine.init_audio_device():
try:
engine.load_screen_sounds("menu")
# Check that screen sounds were loaded
self.assertIn("click", engine.sounds)
self.assertIn("hover", engine.sounds)
# Check that global sounds were loaded
self.assertIn("confirm", engine.sounds)
finally:
engine.close_audio_device()
@patch('libs.audio.get_config')
def test_unload_all_sounds(self, mock_config):
"""Test unloading all sounds."""
mock_config.return_value = {"paths": {"skin": f"{self.sounds_dir}"}}
engine = AudioEngine(0, 44100.0, 512, self.volume_presets, sounds_path=self.sounds_dir)
engine.set_log_level(10)
if engine.init_audio_device():
try:
# Load multiple sounds
engine.load_sound(self.sounds_dir / "test_sound.wav", "s1")
engine.load_sound(self.sounds_dir / "test_sound.wav", "s2")
engine.load_sound(self.sounds_dir / "test_sound.wav", "s3")
self.assertEqual(len(engine.sounds), 3)
# Unload all
engine.unload_all_sounds()
self.assertEqual(len(engine.sounds), 0)
finally:
engine.close_audio_device()
@patch('libs.audio.get_config')
def test_load_and_play_music_stream(self, mock_config):
"""Test loading and playing music streams."""
mock_config.return_value = {"paths": {"skin": f"{self.sounds_dir}"}}
engine = AudioEngine(0, 44100.0, 512, self.volume_presets, sounds_path=self.sounds_dir)
engine.set_log_level(10)
if engine.init_audio_device():
try:
music_path = self.sounds_dir / "test_music.wav"
music_id = engine.load_music_stream(music_path, "bgm")
print(music_id)
self.assertEqual(music_id, "bgm")
self.assertIn("bgm", engine.music_streams)
# Play music
engine.play_music_stream("bgm", "music")
# Update music stream
engine.update_music_stream("bgm")
# Check if playing
is_playing = engine.is_music_stream_playing("bgm")
self.assertIsInstance(is_playing, bool)
# Stop music
engine.stop_music_stream("bgm")
finally:
engine.close_audio_device()
@patch('libs.audio.get_config')
def test_music_time_functions(self, mock_config):
"""Test getting music time length and played."""
mock_config.return_value = {"paths": {"skin": f"{self.sounds_dir}"}}
engine = AudioEngine(0, 44100.0, 512, self.volume_presets, sounds_path=self.sounds_dir)
engine.set_log_level(10)
if engine.init_audio_device():
try:
music_path = self.sounds_dir / "test_music.wav"
music_file = engine.load_music_stream(music_path, "bgm")
# Get time length
length = engine.get_music_time_length(music_file)
self.assertGreater(length, 0.0)
self.assertLess(length, 10.0) # Should be around 2 seconds
# Play and get time played
engine.play_music_stream(music_file, "music")
engine.update_music_stream(music_file)
import time
time.sleep(0.1)
engine.update_music_stream(music_file)
played = engine.get_music_time_played(music_file)
self.assertGreaterEqual(played, 0.0)
finally:
engine.close_audio_device()
@patch('libs.audio.get_config')
def test_music_volume_control(self, mock_config):
"""Test setting music volume."""
mock_config.return_value = {"paths": {"skin": f"{self.sounds_dir}"}}
engine = AudioEngine(0, 44100.0, 512, self.volume_presets, sounds_path=self.sounds_dir)
engine.set_log_level(10)
if engine.init_audio_device():
try:
music_path = self.sounds_dir / "test_music.wav"
engine.load_music_stream(music_path, "bgm")
# Set volume (should not raise exception)
engine.set_music_volume("bgm", 0.6)
finally:
engine.close_audio_device()
@patch('libs.audio.get_config')
def test_seek_music_stream(self, mock_config):
"""Test seeking in music stream."""
mock_config.return_value = {"paths": {"skin": f"{self.sounds_dir}"}}
engine = AudioEngine(0, 44100.0, 512, self.volume_presets, sounds_path=self.sounds_dir)
engine.set_log_level(10)
if engine.init_audio_device():
try:
music_path = self.sounds_dir / "test_music.wav"
engine.load_music_stream(music_path, "bgm")
# Seek to position (should not raise exception)
engine.seek_music_stream("bgm", 0.5)
finally:
engine.close_audio_device()
@patch('libs.audio.get_config')
def test_unload_music_stream(self, mock_config):
"""Test unloading music stream."""
mock_config.return_value = {"paths": {"skin": f"{self.sounds_dir}"}}
engine = AudioEngine(0, 44100.0, 512, self.volume_presets, sounds_path=self.sounds_dir)
engine.set_log_level(10)
if engine.init_audio_device():
try:
music_path = self.sounds_dir / "test_music.wav"
engine.load_music_stream(music_path, "bgm")
self.assertIn("bgm", engine.music_streams)
engine.unload_music_stream("bgm")
self.assertNotIn("bgm", engine.music_streams)
finally:
engine.close_audio_device()
@patch('libs.audio.get_config')
def test_unload_all_music(self, mock_config):
"""Test unloading all music streams."""
mock_config.return_value = {"paths": {"skin": f"{self.sounds_dir}"}}
engine = AudioEngine(0, 44100.0, 512, self.volume_presets, sounds_path=self.sounds_dir)
engine.set_log_level(10)
if engine.init_audio_device():
try:
# Load multiple music streams
music_path = self.sounds_dir / "test_music.wav"
engine.load_music_stream(music_path, "bgm1")
engine.load_music_stream(music_path, "bgm2")
self.assertEqual(len(engine.music_streams), 2)
engine.unload_all_music()
self.assertEqual(len(engine.music_streams), 0)
finally:
engine.close_audio_device()
@patch('libs.audio.get_config')
def test_host_api_functions(self, mock_config):
"""Test host API query functions."""
mock_config.return_value = {"paths": {"skin": f"{self.sounds_dir}"}}
engine = AudioEngine(0, 44100.0, 512, self.volume_presets, sounds_path=self.sounds_dir)
engine.set_log_level(10)
engine.init_audio_device()
# List host APIs (should not crash)
engine.list_host_apis()
# Get host API name
name = engine.get_host_api_name(0)
self.assertIsInstance(name, str)
@patch('libs.audio.get_config')
def test_full_lifecycle(self, mock_config):
"""Test complete audio engine lifecycle."""
mock_config.return_value = {"paths": {"skin": f"{self.sounds_dir}"}}
engine = AudioEngine(0, 44100.0, 512, self.volume_presets, sounds_path=self.sounds_dir)
engine.set_log_level(10)
if engine.init_audio_device():
try:
# Load sounds and music
engine.load_sound(self.sounds_dir / "test_sound.wav", "sfx")
engine.load_music_stream(self.sounds_dir / "test_music.wav", "bgm")
# Set volumes
engine.set_master_volume(0.8)
engine.set_sound_volume("sfx", 0.7)
engine.set_music_volume("bgm", 0.6)
# Play audio
engine.play_sound("sfx", "sound")
engine.play_music_stream("bgm", "music")
import time
time.sleep(0.1)
# Update music
engine.update_music_stream("bgm")
# Stop
engine.stop_sound("sfx")
engine.stop_music_stream("bgm")
# Cleanup
engine.unload_sound("sfx")
engine.unload_music_stream("bgm")
finally:
engine.close_audio_device()
if __name__ == '__main__':
# Run tests
unittest.main(verbosity=2)

View File

@@ -0,0 +1,551 @@
import unittest
from pathlib import Path
from unittest.mock import Mock, patch
from libs.global_data import (
Camera,
Crown,
DanResultData,
DanResultExam,
DanResultSong,
Difficulty,
GlobalData,
Modifiers,
PlayerNum,
ResultData,
ScoreMethod,
SessionData,
global_data,
reset_session,
)
class TestPlayerNum(unittest.TestCase):
"""Test cases for the PlayerNum enum."""
def test_player_num_values(self):
"""Test PlayerNum enum values."""
self.assertEqual(PlayerNum.ALL, 0)
self.assertEqual(PlayerNum.P1, 1)
self.assertEqual(PlayerNum.P2, 2)
self.assertEqual(PlayerNum.TWO_PLAYER, 3)
self.assertEqual(PlayerNum.DAN, 4)
def test_player_num_is_int_enum(self):
"""Test that PlayerNum values are integers."""
self.assertIsInstance(PlayerNum.P1, int)
self.assertIsInstance(PlayerNum.P2, int)
class TestScoreMethod(unittest.TestCase):
"""Test cases for the ScoreMethod class."""
def test_score_method_constants(self):
"""Test ScoreMethod constants."""
self.assertEqual(ScoreMethod.GEN3, "gen3")
self.assertEqual(ScoreMethod.SHINUCHI, "shinuchi")
class TestDifficulty(unittest.TestCase):
"""Test cases for the Difficulty enum."""
def test_difficulty_values(self):
"""Test Difficulty enum values."""
self.assertEqual(Difficulty.EASY, 0)
self.assertEqual(Difficulty.NORMAL, 1)
self.assertEqual(Difficulty.HARD, 2)
self.assertEqual(Difficulty.ONI, 3)
self.assertEqual(Difficulty.URA, 4)
self.assertEqual(Difficulty.TOWER, 5)
self.assertEqual(Difficulty.DAN, 6)
def test_difficulty_ordering(self):
"""Test that difficulty levels are ordered correctly."""
self.assertLess(Difficulty.EASY, Difficulty.NORMAL)
self.assertLess(Difficulty.NORMAL, Difficulty.HARD)
self.assertLess(Difficulty.HARD, Difficulty.ONI)
self.assertLess(Difficulty.ONI, Difficulty.URA)
class TestCrown(unittest.TestCase):
"""Test cases for the Crown enum."""
def test_crown_values(self):
"""Test Crown enum values."""
self.assertEqual(Crown.NONE, 0)
self.assertEqual(Crown.CLEAR, 1)
self.assertEqual(Crown.FC, 2)
self.assertEqual(Crown.DFC, 3)
def test_crown_ordering(self):
"""Test crown achievement ordering."""
self.assertLess(Crown.NONE, Crown.CLEAR)
self.assertLess(Crown.CLEAR, Crown.FC)
self.assertLess(Crown.FC, Crown.DFC)
class TestModifiers(unittest.TestCase):
"""Test cases for the Modifiers dataclass."""
def test_default_values(self):
"""Test default modifier values."""
mods = Modifiers()
self.assertFalse(mods.auto)
self.assertEqual(mods.speed, 1.0)
self.assertFalse(mods.display)
self.assertFalse(mods.inverse)
self.assertEqual(mods.random, 0)
def test_custom_values(self):
"""Test custom modifier values."""
mods = Modifiers(auto=True, speed=2.0, display=True, inverse=True, random=3)
self.assertTrue(mods.auto)
self.assertEqual(mods.speed, 2.0)
self.assertTrue(mods.display)
self.assertTrue(mods.inverse)
self.assertEqual(mods.random, 3)
def test_speed_multiplier(self):
"""Test different speed multiplier values."""
mods1 = Modifiers(speed=0.5)
mods2 = Modifiers(speed=1.5)
mods3 = Modifiers(speed=3.0)
self.assertEqual(mods1.speed, 0.5)
self.assertEqual(mods2.speed, 1.5)
self.assertEqual(mods3.speed, 3.0)
class TestDanResultSong(unittest.TestCase):
"""Test cases for the DanResultSong dataclass."""
def test_default_values(self):
"""Test default DanResultSong values."""
song = DanResultSong()
self.assertEqual(song.selected_difficulty, 0)
self.assertEqual(song.diff_level, 0)
self.assertEqual(song.song_title, "default_title")
self.assertEqual(song.genre_index, 0)
self.assertEqual(song.good, 0)
self.assertEqual(song.ok, 0)
self.assertEqual(song.bad, 0)
self.assertEqual(song.drumroll, 0)
def test_custom_values(self):
"""Test custom DanResultSong values."""
song = DanResultSong(
selected_difficulty=3,
diff_level=10,
song_title="Test Song",
genre_index=5,
good=100,
ok=20,
bad=5,
drumroll=15
)
self.assertEqual(song.selected_difficulty, 3)
self.assertEqual(song.diff_level, 10)
self.assertEqual(song.song_title, "Test Song")
self.assertEqual(song.genre_index, 5)
self.assertEqual(song.good, 100)
self.assertEqual(song.ok, 20)
self.assertEqual(song.bad, 5)
self.assertEqual(song.drumroll, 15)
class TestDanResultExam(unittest.TestCase):
"""Test cases for the DanResultExam class."""
def test_default_values(self):
"""Test default DanResultExam values."""
exam = DanResultExam()
self.assertEqual(exam.progress, 0)
self.assertEqual(exam.counter_value, 0)
self.assertEqual(exam.bar_texture, "exam_red")
self.assertFalse(exam.failed)
def test_custom_values(self):
"""Test custom DanResultExam values."""
exam = DanResultExam()
exam.progress = 0.75
exam.counter_value = 150
exam.bar_texture = "exam_gold"
exam.failed = True
self.assertEqual(exam.progress, 0.75)
self.assertEqual(exam.counter_value, 150)
self.assertEqual(exam.bar_texture, "exam_gold")
self.assertTrue(exam.failed)
class TestDanResultData(unittest.TestCase):
"""Test cases for the DanResultData dataclass."""
def test_default_values(self):
"""Test default DanResultData values."""
data = DanResultData()
self.assertEqual(data.dan_color, 0)
self.assertEqual(data.dan_title, "default_title")
self.assertEqual(data.score, 0)
self.assertEqual(data.gauge_length, 0.0)
self.assertEqual(data.max_combo, 0)
self.assertEqual(data.songs, [])
self.assertEqual(data.exams, [])
self.assertEqual(data.exam_data, [])
def test_with_songs(self):
"""Test DanResultData with songs."""
song1 = DanResultSong(song_title="Song 1")
song2 = DanResultSong(song_title="Song 2")
data = DanResultData(songs=[song1, song2])
self.assertEqual(len(data.songs), 2)
self.assertEqual(data.songs[0].song_title, "Song 1")
self.assertEqual(data.songs[1].song_title, "Song 2")
def test_with_exam_data(self):
"""Test DanResultData with exam data."""
exam1 = DanResultExam()
exam1.progress = 0.5
exam2 = DanResultExam()
exam2.progress = 1.0
data = DanResultData(exam_data=[exam1, exam2])
self.assertEqual(len(data.exam_data), 2)
self.assertEqual(data.exam_data[0].progress, 0.5)
self.assertEqual(data.exam_data[1].progress, 1.0)
class TestResultData(unittest.TestCase):
"""Test cases for the ResultData dataclass."""
def test_default_values(self):
"""Test default ResultData values."""
data = ResultData()
self.assertEqual(data.score, 0)
self.assertEqual(data.good, 0)
self.assertEqual(data.ok, 0)
self.assertEqual(data.bad, 0)
self.assertEqual(data.max_combo, 0)
self.assertEqual(data.total_drumroll, 0)
self.assertEqual(data.gauge_length, 0)
self.assertEqual(data.prev_score, 0)
def test_custom_values(self):
"""Test custom ResultData values."""
data = ResultData(
score=500000,
good=150,
ok=30,
bad=10,
max_combo=120,
total_drumroll=45,
gauge_length=0.85,
prev_score=450000
)
self.assertEqual(data.score, 500000)
self.assertEqual(data.good, 150)
self.assertEqual(data.ok, 30)
self.assertEqual(data.bad, 10)
self.assertEqual(data.max_combo, 120)
self.assertEqual(data.total_drumroll, 45)
self.assertEqual(data.gauge_length, 0.85)
self.assertEqual(data.prev_score, 450000)
def test_total_notes(self):
"""Test calculating total notes from result data."""
data = ResultData(good=100, ok=50, bad=10)
total = data.good + data.ok + data.bad
self.assertEqual(total, 160)
class TestSessionData(unittest.TestCase):
"""Test cases for the SessionData dataclass."""
def test_default_values(self):
"""Test default SessionData values."""
session = SessionData()
self.assertEqual(session.selected_song, Path())
self.assertEqual(session.song_hash, "")
self.assertEqual(session.selected_dan, [])
self.assertEqual(session.selected_dan_exam, [])
self.assertEqual(session.dan_color, 0)
self.assertEqual(session.selected_difficulty, 0)
self.assertEqual(session.song_title, "default_title")
self.assertEqual(session.genre_index, 0)
self.assertIsInstance(session.result_data, ResultData)
self.assertIsInstance(session.dan_result_data, DanResultData)
def test_custom_song_selection(self):
"""Test custom song selection."""
song_path = Path("Songs/TestSong/song.tja")
session = SessionData(
selected_song=song_path,
song_hash="abc123",
selected_difficulty=3,
song_title="Test Song"
)
self.assertEqual(session.selected_song, song_path)
self.assertEqual(session.song_hash, "abc123")
self.assertEqual(session.selected_difficulty, 3)
self.assertEqual(session.song_title, "Test Song")
def test_dan_selection(self):
"""Test dan course selection."""
dan_songs = [(Mock(), 0, 3, 10), (Mock(), 1, 3, 10)]
dan_exams = [Mock(), Mock(), Mock()]
session = SessionData(
selected_dan=dan_songs,
selected_dan_exam=dan_exams,
dan_color=2
)
self.assertEqual(len(session.selected_dan), 2)
self.assertEqual(len(session.selected_dan_exam), 3)
self.assertEqual(session.dan_color, 2)
def test_result_data_independence(self):
"""Test that each session has independent result data."""
session1 = SessionData()
session2 = SessionData()
session1.result_data.score = 100000
self.assertEqual(session1.result_data.score, 100000)
self.assertEqual(session2.result_data.score, 0)
class TestCamera(unittest.TestCase):
"""Test cases for the Camera class."""
@patch('libs.global_data.ray')
def test_default_values(self, mock_ray):
"""Test default Camera values."""
mock_ray.Vector2 = Mock(return_value=Mock())
mock_ray.BLACK = Mock()
camera = Camera()
self.assertEqual(camera.zoom, 1.0)
self.assertEqual(camera.h_scale, 1.0)
self.assertEqual(camera.v_scale, 1.0)
self.assertEqual(camera.rotation, 0.0)
class TestGlobalData(unittest.TestCase):
"""Test cases for the GlobalData dataclass."""
def test_default_values(self):
"""Test default GlobalData values."""
data = GlobalData()
self.assertEqual(data.songs_played, 0)
self.assertIsInstance(data.camera, Camera)
self.assertEqual(data.song_hashes, {})
self.assertEqual(data.song_paths, {})
self.assertEqual(data.score_db, "")
self.assertEqual(data.song_progress, 0.0)
self.assertEqual(data.total_songs, 0)
self.assertEqual(data.hit_sound, [0, 0, 0])
self.assertEqual(data.player_num, PlayerNum.P1)
self.assertEqual(data.input_locked, 0)
def test_modifiers_list(self):
"""Test that modifiers list has correct size."""
data = GlobalData()
self.assertEqual(len(data.modifiers), 3)
self.assertIsInstance(data.modifiers[0], Modifiers)
self.assertIsInstance(data.modifiers[1], Modifiers)
self.assertIsInstance(data.modifiers[2], Modifiers)
def test_session_data_list(self):
"""Test that session data list has correct size."""
data = GlobalData()
self.assertEqual(len(data.session_data), 3)
self.assertIsInstance(data.session_data[0], SessionData)
self.assertIsInstance(data.session_data[1], SessionData)
self.assertIsInstance(data.session_data[2], SessionData)
def test_song_hashes_dict(self):
"""Test song_hashes dictionary operations."""
data = GlobalData()
data.song_hashes["hash1"] = [{"path": "Songs/Song1"}]
data.song_hashes["hash2"] = [{"path": "Songs/Song2"}]
self.assertEqual(len(data.song_hashes), 2)
self.assertIn("hash1", data.song_hashes)
self.assertIn("hash2", data.song_hashes)
def test_song_paths_dict(self):
"""Test song_paths dictionary operations."""
data = GlobalData()
path1 = Path("Songs/Song1/song.tja")
path2 = Path("Songs/Song2/song.tja")
data.song_paths[path1] = "hash1"
data.song_paths[path2] = "hash2"
self.assertEqual(len(data.song_paths), 2)
self.assertEqual(data.song_paths[path1], "hash1")
self.assertEqual(data.song_paths[path2], "hash2")
def test_input_locked_counter(self):
"""Test input_locked as a counter."""
data = GlobalData()
self.assertEqual(data.input_locked, 0)
data.input_locked += 1
self.assertEqual(data.input_locked, 1)
data.input_locked += 1
self.assertEqual(data.input_locked, 2)
data.input_locked -= 1
self.assertEqual(data.input_locked, 1)
def test_songs_played_counter(self):
"""Test songs_played counter."""
data = GlobalData()
self.assertEqual(data.songs_played, 0)
data.songs_played += 1
self.assertEqual(data.songs_played, 1)
data.songs_played += 1
self.assertEqual(data.songs_played, 2)
def test_hit_sound_indices(self):
"""Test hit_sound indices list."""
data = GlobalData()
self.assertEqual(data.hit_sound, [0, 0, 0])
data.hit_sound[0] = 1
data.hit_sound[1] = 2
data.hit_sound[2] = 3
self.assertEqual(data.hit_sound, [1, 2, 3])
class TestGlobalDataSingleton(unittest.TestCase):
"""Test cases for the global_data singleton."""
def test_global_data_exists(self):
"""Test that global_data instance exists."""
self.assertIsInstance(global_data, GlobalData)
def test_global_data_modifiable(self):
"""Test that global_data can be modified."""
original_songs_played = global_data.songs_played
global_data.songs_played += 1
self.assertEqual(global_data.songs_played, original_songs_played + 1)
# Reset for other tests
global_data.songs_played = original_songs_played
class TestResetSession(unittest.TestCase):
"""Test cases for reset_session function."""
def test_reset_session_clears_p1_data(self):
"""Test that reset_session clears player 1 data."""
global_data.session_data[1].result_data.score = 100000
global_data.session_data[1].song_title = "Test Song"
reset_session()
self.assertIsInstance(global_data.session_data[1], SessionData)
self.assertEqual(global_data.session_data[1].song_title, "default_title")
def test_reset_session_clears_p2_data(self):
"""Test that reset_session clears player 2 data."""
global_data.session_data[2].result_data.score = 50000
global_data.session_data[2].selected_difficulty = 3
reset_session()
self.assertIsInstance(global_data.session_data[2], SessionData)
self.assertEqual(global_data.session_data[2].selected_difficulty, 0)
def test_reset_session_preserves_index_0(self):
"""Test that reset_session doesn't affect index 0."""
original_data = global_data.session_data[0]
original_data.song_title = "Should Not Change"
reset_session()
self.assertEqual(global_data.session_data[0].song_title, "Should Not Change")
def test_reset_session_creates_new_instances(self):
"""Test that reset_session creates new SessionData instances."""
old_p1_session = global_data.session_data[1]
old_p2_session = global_data.session_data[2]
reset_session()
self.assertIsNot(global_data.session_data[1], old_p1_session)
self.assertIsNot(global_data.session_data[2], old_p2_session)
class TestDataclassIntegration(unittest.TestCase):
"""Integration tests for dataclass interactions."""
def test_session_with_result_data(self):
"""Test SessionData with populated ResultData."""
session = SessionData()
session.result_data.score = 750000
session.result_data.good = 200
session.result_data.max_combo = 180
self.assertEqual(session.result_data.score, 750000)
self.assertEqual(session.result_data.good, 200)
self.assertEqual(session.result_data.max_combo, 180)
def test_session_with_dan_result_data(self):
"""Test SessionData with populated DanResultData."""
session = SessionData()
session.dan_result_data.dan_title = "10th Dan"
session.dan_result_data.dan_color = 5
song1 = DanResultSong(song_title="Dan Song 1")
song2 = DanResultSong(song_title="Dan Song 2")
session.dan_result_data.songs = [song1, song2]
self.assertEqual(session.dan_result_data.dan_title, "10th Dan")
self.assertEqual(len(session.dan_result_data.songs), 2)
def test_modifiers_independent_per_player(self):
"""Test that each player has independent modifiers."""
data = GlobalData()
data.modifiers[1].speed = 2.0
data.modifiers[2].speed = 1.5
self.assertEqual(data.modifiers[1].speed, 2.0)
self.assertEqual(data.modifiers[2].speed, 1.5)
self.assertEqual(data.modifiers[0].speed, 1.0)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,289 @@
import unittest
from unittest.mock import Mock, patch
from libs.global_data import PlayerNum
from libs.global_objects import (
AllNetIcon,
CoinOverlay,
EntryOverlay,
Indicator,
Nameplate,
Timer,
)
class TestNameplate(unittest.TestCase):
"""Test cases for the Nameplate class."""
def setUp(self):
"""Set up test fixtures."""
# Mock global_tex and its methods
self.mock_tex = Mock()
self.mock_tex.skin_config = {
"nameplate_text_name": Mock(font_size=20, x=100, y=50, width=200),
"nameplate_text_title": Mock(font_size=16, x=100, y=80, width=150),
"nameplate_title_offset": Mock(x=10),
"nameplate_dan_offset": Mock(x=20)
}
self.mock_tex.get_animation = Mock(return_value=Mock(start=Mock(), update=Mock(), is_finished=False))
@patch('libs.global_objects.global_tex')
@patch('libs.global_objects.OutlinedText')
def test_initialization_basic(self, mock_text, mock_global_tex):
"""Test basic nameplate initialization."""
mock_global_tex.skin_config = self.mock_tex.skin_config
nameplate = Nameplate("TestPlayer", "TestTitle", PlayerNum.P1, 5, False, False, 0)
self.assertEqual(nameplate.dan_index, 5)
self.assertEqual(nameplate.player_num, 1)
self.assertFalse(nameplate.is_gold)
self.assertFalse(nameplate.is_rainbow)
self.assertEqual(nameplate.title_bg, 0)
@patch('libs.global_objects.global_tex')
@patch('libs.global_objects.OutlinedText')
def test_initialization_rainbow(self, mock_text, mock_global_tex):
"""Test rainbow nameplate initialization."""
mock_global_tex.skin_config = self.mock_tex.skin_config
mock_animation = Mock()
mock_global_tex.get_animation.return_value = mock_animation
nameplate = Nameplate("Player", "Title", PlayerNum.P1, 3, False, True, 0)
self.assertTrue(nameplate.is_rainbow)
mock_global_tex.get_animation.assert_called_once_with(12)
mock_animation.start.assert_called_once()
@patch('libs.global_objects.global_tex')
def test_update_rainbow_animation(self, mock_global_tex):
"""Test rainbow animation update logic."""
mock_animation = Mock(is_finished=False, update=Mock())
mock_global_tex.get_animation.return_value = mock_animation
mock_global_tex.skin_config = self.mock_tex.skin_config
with patch('libs.global_objects.OutlinedText'):
nameplate = Nameplate("P", "T", PlayerNum.P1, 0, False, True, 0)
nameplate.update(1000.0)
mock_animation.update.assert_called_once_with(1000.0)
@patch('libs.global_objects.global_tex')
def test_update_rainbow_restart(self, mock_global_tex):
"""Test rainbow animation restarts when finished."""
mock_animation = Mock(is_finished=True, update=Mock(), restart=Mock())
mock_global_tex.get_animation.return_value = mock_animation
mock_global_tex.skin_config = self.mock_tex.skin_config
with patch('libs.global_objects.OutlinedText'):
nameplate = Nameplate("P", "T", PlayerNum.P1, 0, False, True, 0)
nameplate.update(1000.0)
mock_animation.restart.assert_called_once()
@patch('libs.global_objects.global_tex')
def test_unload(self, mock_global_tex):
"""Test nameplate resource cleanup."""
mock_global_tex.skin_config = self.mock_tex.skin_config
with patch('libs.global_objects.OutlinedText') as mock_text:
mock_name = Mock()
mock_title = Mock()
mock_text.side_effect = [mock_name, mock_title]
nameplate = Nameplate("P", "T", PlayerNum.P1, 0, False, False, 0)
nameplate.unload()
mock_name.unload.assert_called_once()
mock_title.unload.assert_called_once()
class TestIndicator(unittest.TestCase):
"""Test cases for the Indicator class."""
@patch('libs.global_objects.global_tex')
@patch('libs.global_objects.OutlinedText')
def test_initialization(self, mock_text, mock_global_tex):
"""Test indicator initialization with different states."""
mock_global_tex.get_animation.return_value = Mock()
mock_global_tex.skin_config = {"indicator_text": Mock(text={"en": "Select"}, font_size=20)}
with patch('libs.global_objects.global_data') as mock_data:
mock_data.config = {"general": {"language": "en"}}
indicator = Indicator(Indicator.State.SELECT)
self.assertEqual(indicator.state, Indicator.State.SELECT)
self.assertEqual(mock_global_tex.get_animation.call_count, 3)
@patch('libs.global_objects.global_tex')
def test_update_animations(self, mock_global_tex):
"""Test that all animations update correctly."""
mock_don_fade = Mock()
mock_arrow_move = Mock()
mock_arrow_fade = Mock()
mock_global_tex.get_animation.side_effect = [mock_don_fade, mock_arrow_move, mock_arrow_fade]
mock_global_tex.skin_config = {"indicator_text": Mock(text={"en": "S"}, font_size=20)}
with patch('libs.global_objects.global_data.config', {"general": {"language": "en"}}):
with patch('libs.global_objects.OutlinedText'):
indicator = Indicator(Indicator.State.SKIP)
indicator.update(500.0)
mock_don_fade.update.assert_called_once_with(500.0)
mock_arrow_move.update.assert_called_once_with(500.0)
mock_arrow_fade.update.assert_called_once_with(500.0)
class TestTimer(unittest.TestCase):
"""Test cases for the Timer class."""
@patch('libs.global_objects.get_config')
@patch('libs.global_objects.global_tex')
def test_initialization(self, mock_tex, mock_config):
"""Test timer initialization."""
mock_config.return_value = {"general": {"timer_frozen": False}}
mock_tex.get_animation.return_value = Mock()
mock_func = Mock()
timer = Timer(30, 0.0, mock_func)
self.assertEqual(timer.time, 30)
self.assertEqual(timer.counter, "30")
self.assertFalse(timer.is_finished)
self.assertFalse(timer.is_frozen)
@patch('libs.global_objects.audio')
@patch('libs.global_objects.get_config')
@patch('libs.global_objects.global_tex')
def test_countdown_normal(self, mock_tex, mock_config, mock_audio):
"""Test normal countdown behavior."""
mock_config.return_value = {"general": {"timer_frozen": False}}
mock_tex.get_animation.return_value = Mock(update=Mock(), start=Mock())
mock_func = Mock()
timer = Timer(15, 0.0, mock_func)
timer.update(1000.0)
self.assertEqual(timer.time, 14)
self.assertEqual(timer.counter, "14")
@patch('libs.global_objects.audio')
@patch('libs.global_objects.get_config')
@patch('libs.global_objects.global_tex')
def test_countdown_below_ten(self, mock_tex, mock_config, mock_audio):
"""Test countdown triggers animations below 10."""
mock_config.return_value = {"general": {"timer_frozen": False}}
mock_animation = Mock(update=Mock(), start=Mock())
mock_tex.get_animation.return_value = mock_animation
mock_func = Mock()
timer = Timer(10, 0.0, mock_func)
timer.update(1000.0)
self.assertEqual(timer.time, 9)
mock_audio.play_sound.assert_called_with('timer_blip', 'sound')
self.assertEqual(mock_animation.start.call_count, 3)
@patch('libs.global_objects.audio')
@patch('libs.global_objects.get_config')
@patch('libs.global_objects.global_tex')
def test_voice_triggers(self, mock_tex, mock_config, mock_audio):
"""Test voice announcements at specific times."""
mock_config.return_value = {"general": {"timer_frozen": False}}
mock_tex.get_animation.return_value = Mock(update=Mock(), start=Mock())
mock_func = Mock()
# Test 10 second voice
timer = Timer(11, 0.0, mock_func)
timer.update(1000.0)
mock_audio.play_sound.assert_called_with('voice_timer_10', 'voice')
# Test 5 second voice
timer = Timer(6, 0.0, mock_func)
timer.update(1000.0)
mock_audio.play_sound.assert_called_with('voice_timer_5', 'voice')
@patch('libs.global_objects.audio')
@patch('libs.global_objects.get_config')
@patch('libs.global_objects.global_tex')
def test_timer_finish_callback(self, mock_tex, mock_config, mock_audio):
"""Test callback is triggered when timer reaches zero."""
mock_config.return_value = {"general": {"timer_frozen": False}}
mock_tex.get_animation.return_value = Mock(update=Mock(), start=Mock())
mock_audio.is_sound_playing.return_value = False
mock_func = Mock()
timer = Timer(1, 0.0, mock_func)
timer.update(1000.0)
timer.update(2000.0)
mock_func.assert_called_once()
self.assertTrue(timer.is_finished)
@patch('libs.global_objects.get_config')
@patch('libs.global_objects.global_tex')
def test_timer_frozen(self, mock_tex, mock_config):
"""Test frozen timer doesn't count down."""
mock_config.return_value = {"general": {"timer_frozen": True}}
mock_tex.get_animation.return_value = Mock(update=Mock())
mock_func = Mock()
timer = Timer(10, 0.0, mock_func)
initial_time = timer.time
timer.update(1000.0)
self.assertEqual(timer.time, initial_time)
class TestCoinOverlay(unittest.TestCase):
"""Test cases for the CoinOverlay class."""
@patch('libs.global_objects.global_tex')
@patch('libs.global_objects.global_data')
@patch('libs.global_objects.OutlinedText')
def test_initialization(self, mock_text, mock_data, mock_tex):
"""Test coin overlay initialization."""
mock_tex.skin_config = {
"free_play": Mock(text={"en": "Free Play"}, font_size=24, y=100)
}
mock_data.config = {"general": {"language": "en"}}
_ = CoinOverlay()
mock_text.assert_called_once()
class TestAllNetIcon(unittest.TestCase):
"""Test cases for the AllNetIcon class."""
@patch('libs.global_objects.get_config')
def test_initialization_offline(self, mock_config):
"""Test AllNet icon initializes offline."""
mock_config.return_value = {"general": {"fake_online": False}}
icon = AllNetIcon()
self.assertFalse(icon.online)
@patch('libs.global_objects.get_config')
def test_initialization_online(self, mock_config):
"""Test AllNet icon initializes online."""
mock_config.return_value = {"general": {"fake_online": True}}
icon = AllNetIcon()
self.assertTrue(icon.online)
class TestEntryOverlay(unittest.TestCase):
"""Test cases for the EntryOverlay class."""
@patch('libs.global_objects.get_config')
def test_initialization(self, mock_config):
"""Test entry overlay initialization."""
mock_config.return_value = {"general": {"fake_online": False}}
overlay = EntryOverlay()
self.assertFalse(overlay.online)

333
test/libs/test_screen.py Normal file
View File

@@ -0,0 +1,333 @@
import unittest
from unittest.mock import Mock, patch
from libs.screen import Screen
class TestScreen(unittest.TestCase):
"""Test cases for the Screen class."""
def setUp(self):
"""Set up test fixtures."""
self.screen_name = "test_screen"
@patch('libs.screen.tex')
@patch('libs.screen.audio')
def test_initialization(self, mock_audio, mock_tex):
"""Test screen initialization."""
screen = Screen(self.screen_name)
self.assertEqual(screen.screen_name, self.screen_name)
self.assertFalse(screen.screen_init)
@patch('libs.screen.tex')
@patch('libs.screen.audio')
def test_on_screen_start(self, mock_audio, mock_tex):
"""Test on_screen_start loads textures and sounds."""
screen = Screen(self.screen_name)
screen.on_screen_start()
mock_tex.load_screen_textures.assert_called_once_with(self.screen_name)
mock_audio.load_screen_sounds.assert_called_once_with(self.screen_name)
@patch('libs.screen.tex')
@patch('libs.screen.audio')
def test_do_screen_start_first_call(self, mock_audio, mock_tex):
"""Test _do_screen_start initializes screen on first call."""
screen = Screen(self.screen_name)
self.assertFalse(screen.screen_init)
screen._do_screen_start()
self.assertTrue(screen.screen_init)
mock_tex.load_screen_textures.assert_called_once_with(self.screen_name)
mock_audio.load_screen_sounds.assert_called_once_with(self.screen_name)
@patch('libs.screen.tex')
@patch('libs.screen.audio')
def test_do_screen_start_subsequent_calls(self, mock_audio, mock_tex):
"""Test _do_screen_start doesn't reinitialize on subsequent calls."""
screen = Screen(self.screen_name)
screen._do_screen_start()
screen._do_screen_start()
screen._do_screen_start()
# Should only be called once despite multiple calls
mock_tex.load_screen_textures.assert_called_once()
mock_audio.load_screen_sounds.assert_called_once()
@patch('libs.screen.tex')
@patch('libs.screen.audio')
def test_on_screen_end(self, mock_audio, mock_tex):
"""Test on_screen_end unloads resources and returns next screen."""
screen = Screen(self.screen_name)
screen.screen_init = True
next_screen = "next_screen"
result = screen.on_screen_end(next_screen)
self.assertEqual(result, next_screen)
self.assertFalse(screen.screen_init)
mock_audio.unload_all_sounds.assert_called_once()
mock_audio.unload_all_music.assert_called_once()
mock_tex.unload_textures.assert_called_once()
@patch('libs.screen.tex')
@patch('libs.screen.audio')
def test_on_screen_end_unload_order(self, mock_audio, mock_tex):
"""Test that resources are unloaded in correct order."""
screen = Screen(self.screen_name)
screen.screen_init = True
manager = Mock()
manager.attach_mock(mock_audio.unload_all_sounds, 'unload_sounds')
manager.attach_mock(mock_audio.unload_all_music, 'unload_music')
manager.attach_mock(mock_tex.unload_textures, 'unload_textures')
screen.on_screen_end("next")
# Verify order: sounds, music, then textures
calls = manager.mock_calls
self.assertEqual(calls[0][0], 'unload_sounds')
self.assertEqual(calls[1][0], 'unload_music')
self.assertEqual(calls[2][0], 'unload_textures')
@patch('libs.screen.tex')
@patch('libs.screen.audio')
def test_update_not_initialized(self, mock_audio, mock_tex):
"""Test update initializes screen if not already initialized."""
screen = Screen(self.screen_name)
self.assertFalse(screen.screen_init)
screen.update()
self.assertTrue(screen.screen_init)
mock_tex.load_screen_textures.assert_called_once()
@patch('libs.screen.tex')
@patch('libs.screen.audio')
def test_update_already_initialized(self, mock_audio, mock_tex):
"""Test update doesn't reinitialize if already initialized."""
screen = Screen(self.screen_name)
screen.screen_init = True
screen.update()
# Should not load again
mock_tex.load_screen_textures.assert_not_called()
mock_audio.load_screen_sounds.assert_not_called()
@patch('libs.screen.tex')
@patch('libs.screen.audio')
def test_update_returns_value(self, mock_audio, mock_tex):
"""Test update returns value from _do_screen_start."""
screen = Screen(self.screen_name)
with patch.object(screen, '_do_screen_start', return_value="test_value"):
result = screen.update()
self.assertEqual(result, "test_value")
@patch('libs.screen.tex')
@patch('libs.screen.audio')
def test_draw_default_implementation(self, mock_audio, mock_tex):
"""Test draw has empty default implementation."""
screen = Screen(self.screen_name)
# Should not raise any errors
screen.draw()
@patch('libs.screen.tex')
@patch('libs.screen.audio')
def test_do_draw_when_initialized(self, mock_audio, mock_tex):
"""Test _do_draw calls draw when screen is initialized."""
screen = Screen(self.screen_name)
screen.screen_init = True
with patch.object(screen, 'draw') as mock_draw:
screen._do_draw()
mock_draw.assert_called_once()
@patch('libs.screen.tex')
@patch('libs.screen.audio')
def test_do_draw_when_not_initialized(self, mock_audio, mock_tex):
"""Test _do_draw doesn't call draw when screen is not initialized."""
screen = Screen(self.screen_name)
screen.screen_init = False
with patch.object(screen, 'draw') as mock_draw:
screen._do_draw()
mock_draw.assert_not_called()
class TestScreenSubclass(unittest.TestCase):
"""Test cases for Screen subclass behavior."""
@patch('libs.screen.tex')
@patch('libs.screen.audio')
def test_subclass_custom_on_screen_start(self, mock_audio, mock_tex):
"""Test that subclass can override on_screen_start."""
class CustomScreen(Screen):
def __init__(self, name):
super().__init__(name)
self.custom_init_called = False
def on_screen_start(self):
super().on_screen_start()
self.custom_init_called = True
screen = CustomScreen("custom")
screen.on_screen_start()
self.assertTrue(screen.custom_init_called)
mock_tex.load_screen_textures.assert_called_once_with("custom")
@patch('libs.screen.tex')
@patch('libs.screen.audio')
def test_subclass_custom_update(self, mock_audio, mock_tex):
"""Test that subclass can override update."""
class CustomScreen(Screen):
def __init__(self, name):
super().__init__(name)
self.update_count = 0
def update(self):
result = super().update()
self.update_count += 1
return result
screen = CustomScreen("custom")
screen.update()
screen.update()
self.assertEqual(screen.update_count, 2)
@patch('libs.screen.tex')
@patch('libs.screen.audio')
def test_subclass_custom_draw(self, mock_audio, mock_tex):
"""Test that subclass can override draw."""
class CustomScreen(Screen):
def __init__(self, name):
super().__init__(name)
self.draw_called = False
def draw(self):
self.draw_called = True
screen = CustomScreen("custom")
screen.screen_init = True
screen._do_draw()
self.assertTrue(screen.draw_called)
@patch('libs.screen.tex')
@patch('libs.screen.audio')
def test_subclass_custom_on_screen_end(self, mock_audio, mock_tex):
"""Test that subclass can override on_screen_end."""
class CustomScreen(Screen):
def __init__(self, name):
super().__init__(name)
self.cleanup_called = False
def on_screen_end(self, next_screen):
self.cleanup_called = True
return super().on_screen_end(next_screen)
screen = CustomScreen("custom")
screen.screen_init = True
result = screen.on_screen_end("next")
self.assertTrue(screen.cleanup_called)
self.assertEqual(result, "next")
class TestScreenLifecycle(unittest.TestCase):
"""Test cases for complete screen lifecycle."""
@patch('libs.screen.tex')
@patch('libs.screen.audio')
def test_full_lifecycle(self, mock_audio, mock_tex):
"""Test complete screen lifecycle from start to end."""
screen = Screen("lifecycle_test")
# Initial state
self.assertFalse(screen.screen_init)
# Start screen
screen.update()
self.assertTrue(screen.screen_init)
mock_tex.load_screen_textures.assert_called_once_with("lifecycle_test")
mock_audio.load_screen_sounds.assert_called_once_with("lifecycle_test")
# Multiple updates don't reinitialize
screen.update()
screen.update()
self.assertEqual(mock_tex.load_screen_textures.call_count, 1)
# Draw while initialized
with patch.object(screen, 'draw') as mock_draw:
screen._do_draw()
mock_draw.assert_called_once()
# End screen
result = screen.on_screen_end("next_screen")
self.assertEqual(result, "next_screen")
self.assertFalse(screen.screen_init)
mock_audio.unload_all_sounds.assert_called_once()
mock_audio.unload_all_music.assert_called_once()
mock_tex.unload_textures.assert_called_once()
@patch('libs.screen.tex')
@patch('libs.screen.audio')
def test_multiple_screen_transitions(self, mock_audio, mock_tex):
"""Test transitioning between multiple screens."""
screen1 = Screen("screen1")
screen2 = Screen("screen2")
screen3 = Screen("screen3")
# Initialize first screen
screen1.update()
self.assertTrue(screen1.screen_init)
# Transition to second screen
next_name = screen1.on_screen_end("screen2")
self.assertEqual(next_name, "screen2")
self.assertFalse(screen1.screen_init)
screen2.update()
self.assertTrue(screen2.screen_init)
# Transition to third screen
next_name = screen2.on_screen_end("screen3")
self.assertEqual(next_name, "screen3")
self.assertFalse(screen2.screen_init)
screen3.update()
self.assertTrue(screen3.screen_init)
@patch('libs.screen.tex')
@patch('libs.screen.audio')
def test_screen_reinitialize_after_end(self, mock_audio, mock_tex):
"""Test that screen can be reinitialized after ending."""
screen = Screen("reinit_test")
# First initialization
screen.update()
self.assertTrue(screen.screen_init)
# End screen
screen.on_screen_end("next")
self.assertFalse(screen.screen_init)
# Reinitialize
mock_tex.load_screen_textures.reset_mock()
mock_audio.load_screen_sounds.reset_mock()
screen.update()
self.assertTrue(screen.screen_init)
mock_tex.load_screen_textures.assert_called_once()
mock_audio.load_screen_sounds.assert_called_once()
if __name__ == '__main__':
unittest.main()

289
test/libs/test_texture.py Normal file
View File

@@ -0,0 +1,289 @@
import unittest
from unittest.mock import Mock, patch
from libs.texture import (
FramedTexture,
SkinInfo,
Texture,
TextureWrapper,
)
class TestSkinInfo(unittest.TestCase):
"""Test cases for the SkinInfo dataclass."""
def test_initialization(self):
"""Test SkinInfo initialization."""
skin_info = SkinInfo(
x=100.0,
y=200.0,
font_size=24,
width=300.0,
height=100.0,
text={"en": "Test", "ja": "テスト"}
)
self.assertEqual(skin_info.x, 100.0)
self.assertEqual(skin_info.y, 200.0)
self.assertEqual(skin_info.font_size, 24)
self.assertEqual(skin_info.width, 300.0)
self.assertEqual(skin_info.height, 100.0)
self.assertEqual(skin_info.text, {"en": "Test", "ja": "テスト"})
def test_repr(self):
"""Test SkinInfo string representation."""
skin_info = SkinInfo(
x=100.0,
y=200.0,
font_size=24,
width=300.0,
height=100.0,
text={"en": "Test"}
)
repr_str = repr(skin_info)
self.assertIn("100.0", repr_str)
self.assertIn("200.0", repr_str)
class TestTexture(unittest.TestCase):
"""Test cases for the Texture class."""
def setUp(self):
"""Set up test fixtures."""
self.mock_texture = Mock()
self.mock_texture.width = 100
self.mock_texture.height = 50
@patch('libs.texture.ray')
def test_initialization_single_texture(self, mock_ray):
"""Test Texture initialization with single texture."""
texture = Texture(
name="test_texture",
texture=self.mock_texture,
init_vals={}
)
self.assertEqual(texture.name, "test_texture")
self.assertEqual(texture.texture, self.mock_texture)
self.assertEqual(texture.width, 100)
self.assertEqual(texture.height, 50)
self.assertEqual(texture.x, [0])
self.assertEqual(texture.y, [0])
@patch('libs.texture.ray')
def test_initialization_with_init_vals(self, mock_ray):
"""Test Texture initialization with init_vals."""
init_vals = {"x": 10, "y": 20}
texture = Texture(
name="test",
texture=self.mock_texture,
init_vals=init_vals
)
self.assertEqual(texture.init_vals, init_vals)
self.assertEqual(texture.name, "test")
@patch('libs.texture.ray')
def test_default_values(self, mock_ray):
"""Test Texture default values."""
texture = Texture(name="test", texture=self.mock_texture, init_vals={})
self.assertEqual(texture.x, [0])
self.assertEqual(texture.y, [0])
self.assertEqual(texture.x2, [100])
self.assertEqual(texture.y2, [50])
self.assertEqual(texture.controllable, [False])
@patch('libs.texture.ray')
def test_repr(self, mock_ray):
"""Test Texture string representation."""
texture = Texture(name="test", texture=self.mock_texture, init_vals={})
repr_str = repr(texture)
self.assertIn("test", repr_str)
class TestFramedTexture(unittest.TestCase):
"""Test cases for the FramedTexture class."""
def setUp(self):
"""Set up test fixtures."""
self.mock_textures = [Mock() for _ in range(4)]
for tex in self.mock_textures:
tex.width = 200
tex.height = 100
@patch('libs.texture.ray')
def test_initialization(self, mock_ray):
"""Test FramedTexture initialization."""
framed = FramedTexture(
name="test_framed",
texture=self.mock_textures,
init_vals={}
)
self.assertEqual(framed.name, "test_framed")
self.assertEqual(framed.texture, self.mock_textures)
self.assertEqual(framed.width, 200)
self.assertEqual(framed.height, 100)
self.assertEqual(framed.x, [0])
self.assertEqual(framed.y, [0])
@patch('libs.texture.ray')
def test_default_values(self, mock_ray):
"""Test FramedTexture default values."""
framed = FramedTexture(
name="test",
texture=self.mock_textures,
init_vals={}
)
self.assertEqual(framed.x, [0])
self.assertEqual(framed.y, [0])
self.assertEqual(framed.x2, [200])
self.assertEqual(framed.y2, [100])
class TestTextureWrapper(unittest.TestCase):
"""Test cases for the TextureWrapper class."""
def setUp(self):
"""Set up test fixtures."""
self.mock_texture = Mock()
self.mock_texture.width = 100
self.mock_texture.height = 50
@patch('libs.texture.get_config')
@patch('libs.texture.Path')
def test_initialization(self, mock_path_cls, mock_get_config):
"""Test TextureWrapper initialization."""
mock_get_config.return_value = {'paths': {'skin': 'TestSkin'}}
# Mock the skin_config.json file
mock_path_instance = Mock()
mock_config_path = Mock()
mock_config_path.exists.return_value = True
mock_config_path.read_text.return_value = '{"screen": {"width": 1280, "height": 720}}'
mock_path_instance.__truediv__ = Mock(return_value=mock_config_path)
mock_path_cls.return_value = mock_path_instance
wrapper = TextureWrapper()
self.assertEqual(wrapper.screen_width, 1280)
self.assertEqual(wrapper.screen_height, 720)
self.assertIsInstance(wrapper.textures, dict)
@patch('libs.texture.get_config')
@patch('libs.texture.Path')
def test_get_animation(self, mock_path_cls, mock_get_config):
"""Test getting animation from list."""
mock_get_config.return_value = {'paths': {'skin': 'TestSkin'}}
# Mock the skin_config.json file
mock_path_instance = Mock()
mock_config_path = Mock()
mock_config_path.exists.return_value = True
mock_config_path.read_text.return_value = '{"screen": {"width": 1280, "height": 720}}'
mock_path_instance.__truediv__ = Mock(return_value=mock_config_path)
mock_path_cls.return_value = mock_path_instance
mock_animation = Mock()
wrapper = TextureWrapper()
wrapper.animations = {0: mock_animation}
result = wrapper.get_animation(0)
self.assertEqual(result, mock_animation)
@patch('libs.texture.get_config')
@patch('libs.texture.Path')
@patch('libs.texture.copy.deepcopy')
def test_get_animation_copy(self, mock_deepcopy, mock_path_cls, mock_get_config):
"""Test getting animation copy."""
mock_get_config.return_value = {'paths': {'skin': 'TestSkin'}}
# Mock the skin_config.json file
mock_path_instance = Mock()
mock_config_path = Mock()
mock_config_path.exists.return_value = True
mock_config_path.read_text.return_value = '{"screen": {"width": 1280, "height": 720}}'
mock_path_instance.__truediv__ = Mock(return_value=mock_config_path)
mock_path_cls.return_value = mock_path_instance
mock_animation = Mock()
mock_copy = Mock()
mock_deepcopy.return_value = mock_copy
wrapper = TextureWrapper()
wrapper.animations = {0: mock_animation}
result = wrapper.get_animation(0, is_copy=True)
mock_deepcopy.assert_called_once_with(mock_animation)
self.assertEqual(result, mock_copy)
@patch('libs.texture.get_config')
@patch('libs.texture.Path')
@patch('libs.texture.ray')
def test_read_tex_obj_data(self, mock_ray, mock_path_cls, mock_get_config):
"""Test reading texture object data from JSON."""
mock_get_config.return_value = {'paths': {'skin': 'TestSkin'}}
# Mock the skin_config.json file
mock_path_instance = Mock()
mock_config_path = Mock()
mock_config_path.exists.return_value = True
mock_config_path.read_text.return_value = '{"screen": {"width": 1280, "height": 720}}'
mock_path_instance.__truediv__ = Mock(return_value=mock_config_path)
mock_path_cls.return_value = mock_path_instance
wrapper = TextureWrapper()
# Create a mock texture object
mock_texture = Mock()
mock_texture.x = [0]
mock_texture.y = [0]
# Test with a dictionary mapping
tex_mapping = {"x": 10, "y": 20}
wrapper._read_tex_obj_data(tex_mapping, mock_texture)
# Verify the texture attributes were updated (they are lists)
self.assertEqual(mock_texture.x, [10])
self.assertEqual(mock_texture.y, [20])
@patch('libs.texture.get_config')
@patch('libs.texture.Path')
def test_read_tex_obj_data_not_exists(self, mock_path_cls, mock_get_config):
"""Test reading texture data with empty mapping."""
mock_get_config.return_value = {'paths': {'skin': 'TestSkin'}}
# Mock the skin_config.json file
mock_path_instance = Mock()
mock_config_path = Mock()
mock_config_path.exists.return_value = True
mock_config_path.read_text.return_value = '{"screen": {"width": 1280, "height": 720}}'
mock_path_instance.__truediv__ = Mock(return_value=mock_config_path)
mock_path_cls.return_value = mock_path_instance
wrapper = TextureWrapper()
# Create a mock texture object
mock_texture = Mock()
mock_texture.x = [0]
mock_texture.y = [0]
# Test with empty mapping (should not modify texture)
tex_mapping = {}
wrapper._read_tex_obj_data(tex_mapping, mock_texture)
# Verify the texture attributes remained unchanged
self.assertEqual(mock_texture.x, [0])
self.assertEqual(mock_texture.y, [0])
if __name__ == '__main__':
unittest.main()