"""Tests for Player model and save slot system.""" import tempfile from pathlib import Path from unittest.mock import patch from core.player import ( Player, load_player, save_player, get_active_slot, set_active_slot, list_save_slots, switch_slot, delete_slot, rename_player, _slot_path, _get_slots_dir, _get_active_slot_file, ) def _make_temp_player(tmpdir): """Create a Player with temp paths.""" p = Player() p.name = "测试角色" p.level = 3 p.xp = 150 p.xp_to_next = 300 return p def test_player_xp_and_level_up(): """Test XP addition and level-up mechanics.""" p = Player() p.level = 1 p.xp = 0 p.xp_to_next = 100 # Add XP without leveling result = p.add_xp(50) assert result is None assert p.xp == 50 # Add XP that triggers level up result = p.add_xp(60) assert result is not None assert "Lv.2" in result assert p.level == 2 assert p.xp == 10 # 110 - 100 assert p.xp_to_next == 200 # level * 100 assert p.max_hp == 110 # +10 assert p.hp == 110 # Full heal on level up def test_player_hp_mp(): """Test HP/MP heal and damage.""" p = Player() p.hp = 50 p.max_hp = 100 p.mp = 30 p.max_mp = 100 p.heal(hp=20, mp=10) assert p.hp == 70 assert p.mp == 40 # Can't exceed max p.heal(hp=100, mp=100) assert p.hp == 100 assert p.mp == 100 p.take_damage(80) assert p.hp == 20 # Can't go below 0 p.take_damage(999) assert p.hp == 0 def test_player_inventory(): """Test item add/remove/has.""" p = Player() p.add_item("水晶碎片", "发光的碎片", "key") assert p.has_item("水晶碎片") assert p.inventory_count == 1 # Stack p.add_item("水晶碎片", "发光的碎片", "key") assert p.inventory_count == 2 # Remove one assert p.remove_item("水晶碎片") assert p.inventory_count == 1 # Remove last assert p.remove_item("水晶碎片") assert not p.has_item("水晶碎片") assert p.inventory_count == 0 # Remove non-existent assert not p.remove_item("不存在的物品") def test_player_abilities(): """Test ability learning.""" p = Player() p.learn_ability("飞行能力", "寻找水晶碎片") assert len(p.abilities) == 1 assert p.abilities[0]["name"] == "飞行能力" # Don't duplicate p.learn_ability("飞行能力", "other") assert len(p.abilities) == 1 def test_player_quest_completion(): """Test quest completion tracking.""" p = Player() assert not p.is_quest_completed("q1") p.complete_quest("q1") assert p.is_quest_completed("q1") # Don't duplicate p.complete_quest("q1") assert p.completed_quests.count("q1") == 1 def test_player_serialization(): """Test to_dict/from_dict roundtrip.""" p = Player() p.name = "存档测试" p.level = 5 p.stats["智慧"] = 18 p.add_item("星辰之书", "古老典籍", "key") p.learn_ability("深海呼吸", "寻找命运之书") p.complete_quest("q_abc") d = p.to_dict() p2 = Player.from_dict(d) assert p2.name == "存档测试" assert p2.level == 5 assert p2.stats["智慧"] == 18 assert len(p2.inventory) == 1 assert p2.inventory[0]["name"] == "星辰之书" assert len(p2.abilities) == 1 assert p2.completed_quests == ["q_abc"] def test_player_percent_properties(): """Test HP/MP/XP percent calculations.""" p = Player() p.hp = 75 p.max_hp = 100 p.mp = 50 p.max_mp = 100 p.xp = 30 p.xp_to_next = 100 assert p.hp_percent == 75 assert p.mp_percent == 50 assert p.xp_percent == 30 def test_save_slot_persistence(): """Test save/load with slot system.""" with tempfile.TemporaryDirectory() as tmpdir: with patch("core.player.PLAYER_FILE", Path(tmpdir) / "player.json"), \ patch("core.player._get_slots_dir", lambda: Path(tmpdir) / "saves"), \ patch("core.player._get_active_slot_file", lambda: Path(tmpdir) / "active_slot.txt"): # Save to slot 0 p = Player() p.name = "存档A" p.level = 3 save_player(p) # Load from slot 0 loaded = load_player() assert loaded.name == "存档A" assert loaded.level == 3 # Switch to slot 1 p2 = switch_slot(1) assert p2.name == "梦境行者" # Default new player p2.name = "存档B" p2.level = 7 save_player(p2) # List slots slots = list_save_slots() assert len(slots) == 5 active_slots = [s for s in slots if s["level"] > 0] assert len(active_slots) == 2 # Switch back to slot 0 p_back = switch_slot(0) assert p_back.name == "存档A" assert p_back.level == 3 # Current active should be 0 assert get_active_slot() == 0 def test_save_slot_delete(): """Test deleting a save slot.""" with tempfile.TemporaryDirectory() as tmpdir: with patch("core.player.PLAYER_FILE", Path(tmpdir) / "player.json"), \ patch("core.player._get_slots_dir", lambda: Path(tmpdir) / "saves"), \ patch("core.player._get_active_slot_file", lambda: Path(tmpdir) / "active_slot.txt"): p = Player() p.name = "待删除" save_player(p) assert load_player().name == "待删除" delete_slot(0) # Should get default player after delete fresh = load_player() assert fresh.name == "梦境行者" def test_rename_player(): """Test renaming the current player.""" with tempfile.TemporaryDirectory() as tmpdir: with patch("core.player.PLAYER_FILE", Path(tmpdir) / "player.json"), \ patch("core.player._get_slots_dir", lambda: Path(tmpdir) / "saves"), \ patch("core.player._get_active_slot_file", lambda: Path(tmpdir) / "active_slot.txt"): save_player(Player()) rename_player("新名字") assert load_player().name == "新名字" if __name__ == "__main__": test_player_xp_and_level_up() test_player_hp_mp() test_player_inventory() test_player_abilities() test_player_quest_completion() test_player_serialization() test_player_percent_properties() test_save_slot_persistence() test_save_slot_delete() test_rename_player() print("All player tests passed!")