import os import pathlib import tempfile import unittest os.environ.setdefault("SDL_VIDEODRIVER", "dummy") import engine.collection as collection from engine.plants import POOL, make from engine.unit import TEAM_PLAYER class ExtractionTest(unittest.TestCase): @classmethod def setUpClass(cls): cls._orig = collection._DATA_FILE fd, cls._path = tempfile.mkstemp(suffix=".json") os.close(fd) collection._DATA_FILE = pathlib.Path(cls._path) @classmethod def tearDownClass(cls): collection._DATA_FILE = cls._orig if os.path.exists(cls._path): os.unlink(cls._path) def setUp(self): data = collection.load() data["owned"] = ["Lavender", "Zinnia", "Marigold", "Sunflower", "Hydrangea", "Hellebore"] for n in data["owned"]: data["progress"].setdefault(n, {"level": 1, "stage": 1, "xp": 0, "duplicates": 0, "watered_at": 0}) data["extracted"] = [] for n in data["owned"]: data["progress"][n].pop("injected", None) collection.save(data) def _maxed(self, name): data = collection.load() data["progress"][name]["level"] = 50 data["progress"][name]["stage"] = 3 collection.save(data) def test_extraction_requires_maxed(self): self.assertNotEqual(collection.can_extract("Lavender"), "") self._maxed("Lavender") self.assertEqual(collection.can_extract("Lavender"), "") def test_extract_excludes_basic_and_blocks_inject_of_self_type(self): self._maxed("Lavender") idxs = [i for i, _ in collection.extractable_skills("Lavender")] self.assertTrue(all(i > 0 for i in idxs)) def test_hydrangea_has_no_extractable_abilities(self): self._maxed("Hydrangea") self.assertEqual(collection.extractable_skills("Hydrangea"), []) self.assertNotEqual(collection.can_extract("Hydrangea"), "") def test_extract_resets_plant_and_stores_ability(self): self._maxed("Lavender") idx, sk = collection.extractable_skills("Lavender")[0] self.assertTrue(collection.extract_skill("Lavender", idx)) prog = collection.load()["progress"]["Lavender"] self.assertEqual(prog["level"], 1) self.assertEqual(prog["stage"], 1) ext = collection.extracted_skills() self.assertEqual(len(ext), 1) self.assertEqual(ext[0]["skill_name"], sk.name) self.assertEqual(ext[0]["role"], POOL["Lavender"]["role"]) # extraction again is blocked (no longer maxed) self.assertNotEqual(collection.can_extract("Lavender"), "") def test_inject_requires_same_role_and_is_single_use(self): self._maxed("Lavender") idx, _ = collection.extractable_skills("Lavender")[0] collection.extract_skill("Lavender", idx) rec = collection.extracted_skills()[0] self.assertNotEqual(collection.can_inject("Marigold", rec), "") # tank self.assertEqual(collection.can_inject("Zinnia", rec), "") # attacker self.assertTrue(collection.inject_skill(0, "Zinnia", 1)) self.assertEqual(collection.extracted_skills(), []) # consumed # one injected ability per plant self.assertNotEqual(collection.can_inject("Zinnia", rec), "") self.assertFalse(collection.inject_skill(0, "Zinnia", 2)) def test_unit_uses_injected_skill_and_remove_reverts(self): self._maxed("Lavender") idx, sk = collection.extractable_skills("Lavender")[0] collection.extract_skill("Lavender", idx) collection.inject_skill(0, "Zinnia", 1) unit = make("Zinnia", TEAM_PLAYER) self.assertEqual(unit.skills[1].name, sk.name) self.assertEqual(unit.skills[0].name, POOL["Zinnia"]["skills"][0].name) self.assertTrue(collection.remove_injection("Zinnia")) unit = make("Zinnia", TEAM_PLAYER) self.assertEqual(unit.skills[1].name, POOL["Zinnia"]["skills"][1].name) def test_madness_is_not_extractable(self): self._maxed("Hellebore") names = [sk.name for _, sk in collection.extractable_skills("Hellebore")] self.assertNotIn("Madness", names) idx = POOL["Hellebore"]["skills"].index(next(s for s in POOL["Hellebore"]["skills"] if s.name == "Madness")) self.assertFalse(collection.extract_skill("Hellebore", idx)) def test_inject_cannot_replace_same_ability(self): from engine.skills import skill_to_dict data = collection.load() data["extracted"] = [{ "source": "Lavender", "role": "attacker", "skill_name": "Glitter", "skill": skill_to_dict(POOL["Zinnia"]["skills"][1]), }] collection.save(data) rec = collection.extracted_skills()[0] # Zinnia's 2nd skill IS Glitter -> cannot replace it with Glitter self.assertNotEqual(collection.injection_slot_reason("Zinnia", rec, 1), "") self.assertFalse(collection.inject_skill(0, "Zinnia", 1)) # its 3rd skill (Glimmer) is fine self.assertEqual(collection.injection_slot_reason("Zinnia", rec, 2), "") self.assertTrue(collection.inject_skill(0, "Zinnia", 2)) class FertilizerRewardTest(unittest.TestCase): @classmethod def setUpClass(cls): cls._orig = collection._DATA_FILE fd, cls._path = tempfile.mkstemp(suffix=".json") os.close(fd) collection._DATA_FILE = pathlib.Path(cls._path) @classmethod def tearDownClass(cls): collection._DATA_FILE = cls._orig if os.path.exists(cls._path): os.unlink(cls._path) def test_win_grants_fertilizer_not_water(self): data = collection.load() data["owned"] = ["Lavender", "Marigold", "Sunflower", "Zinnia"] for n in data["owned"]: data["progress"].setdefault(n, {"level": 1, "stage": 1, "xp": 0, "duplicates": 0, "watered_at": 0}) data["water"] = 0 data["fertilizer"] = 0 collection.save(data) collection.grant_xp(["Lavender"], 40, count_battle=True, fertilizer=3) self.assertEqual(collection.available_fertilizer(), 3) self.assertEqual(collection.available_water(), 0) def test_win_fertilizer_scales_with_floor(self): data = collection.load() data["owned"] = ["Lavender", "Marigold", "Sunflower", "Zinnia"] for n in data["owned"]: data["progress"].setdefault(n, {"level": 1, "stage": 1, "xp": 0, "duplicates": 0, "watered_at": 0}) data["fertilizer"] = 0 collection.save(data) # floor 1 -> 1 fertilizer, floor 5 -> 5 collection.grant_xp(["Lavender"], 40, count_battle=True, fertilizer=1) collection.grant_xp(["Lavender"], 40, count_battle=True, fertilizer=5) self.assertEqual(collection.available_fertilizer(), 6) def test_water_still_levels_plants(self): data = collection.load() data["owned"] = ["Lavender", "Marigold", "Sunflower", "Zinnia"] for n in data["owned"]: data["progress"].setdefault(n, {"level": 1, "stage": 1, "xp": 0, "duplicates": 0, "watered_at": 0}) data["water"] = 3 collection.save(data) collection.water("Lavender") self.assertEqual(collection.get_progress("Lavender")["level"], 2) self.assertEqual(collection.available_water(), 2) def test_smart_water_candidate(self): data = collection.load() data["owned"] = ["Lavender", "Marigold", "Sunflower", "Zinnia"] for n in data["owned"]: data["progress"].setdefault(n, {"level": 1, "stage": 1, "xp": 0, "duplicates": 0, "watered_at": 0}) data["progress"]["Marigold"]["duplicates"] = 5 # >= 2 needed for stage 2 collection.save(data) self.assertTrue(collection.smart_water_candidate("Marigold")) data = collection.load() data["progress"]["Marigold"]["level"] = 10 # already at the milestone collection.save(data) self.assertFalse(collection.smart_water_candidate("Marigold")) class StarterPackTest(unittest.TestCase): @classmethod def setUpClass(cls): cls._orig = collection._DATA_FILE fd, cls._path = tempfile.mkstemp(suffix=".json") os.close(fd) collection._DATA_FILE = pathlib.Path(cls._path) @classmethod def tearDownClass(cls): collection._DATA_FILE = cls._orig if os.path.exists(cls._path): os.unlink(cls._path) def setUp(self): if collection._DATA_FILE.exists(): collection._DATA_FILE.unlink() def test_init_starter_pinwheel(self): self.assertFalse(collection.save_exists()) self.assertFalse(collection.init_starter_pinwheel("nope")) self.assertTrue(collection.init_starter_pinwheel("radiant")) self.assertTrue(collection.save_exists()) data = collection.load() self.assertEqual(set(data["owned"]), {"Pinwheel"}) self.assertEqual(data["progress"]["Pinwheel"]["type"], "radiant") self.assertEqual(data["progress"]["Pinwheel"]["level"], 1) self.assertEqual(data["progress"]["Pinwheel"]["stage"], 1) self.assertEqual(data["progress"]["Pinwheel"]["duplicates"], 0) def test_every_type_can_start(self): from engine.types import ALL_TYPES for pt in ALL_TYPES: if collection._DATA_FILE.exists(): collection._DATA_FILE.unlink() self.assertTrue(collection.init_starter_pinwheel(pt)) self.assertEqual(collection.load()["progress"]["Pinwheel"]["type"], pt) if __name__ == "__main__": unittest.main()