| """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 |
|
|
| |
| result = p.add_xp(50) |
| assert result is None |
| assert p.xp == 50 |
|
|
| |
| result = p.add_xp(60) |
| assert result is not None |
| assert "Lv.2" in result |
| assert p.level == 2 |
| assert p.xp == 10 |
| assert p.xp_to_next == 200 |
| assert p.max_hp == 110 |
| assert p.hp == 110 |
|
|
|
|
| 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 |
|
|
| |
| p.heal(hp=100, mp=100) |
| assert p.hp == 100 |
| assert p.mp == 100 |
|
|
| p.take_damage(80) |
| assert p.hp == 20 |
|
|
| |
| 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 |
|
|
| |
| p.add_item("水晶碎片", "发光的碎片", "key") |
| assert p.inventory_count == 2 |
|
|
| |
| assert p.remove_item("水晶碎片") |
| assert p.inventory_count == 1 |
|
|
| |
| assert p.remove_item("水晶碎片") |
| assert not p.has_item("水晶碎片") |
| assert p.inventory_count == 0 |
|
|
| |
| 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"] == "飞行能力" |
|
|
| |
| 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") |
|
|
| |
| 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"): |
|
|
| |
| p = Player() |
| p.name = "存档A" |
| p.level = 3 |
| save_player(p) |
|
|
| |
| loaded = load_player() |
| assert loaded.name == "存档A" |
| assert loaded.level == 3 |
|
|
| |
| p2 = switch_slot(1) |
| assert p2.name == "梦境行者" |
| p2.name = "存档B" |
| p2.level = 7 |
| save_player(p2) |
|
|
| |
| slots = list_save_slots() |
| assert len(slots) == 5 |
| active_slots = [s for s in slots if s["level"] > 0] |
| assert len(active_slots) == 2 |
|
|
| |
| p_back = switch_slot(0) |
| assert p_back.name == "存档A" |
| assert p_back.level == 3 |
|
|
| |
| 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) |
|
|
| |
| 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!") |
|
|