From 42ba6a807cf136a6caa75731d4ce625e8c53a68c Mon Sep 17 00:00:00 2001 From: qwint Date: Fri, 20 Sep 2024 13:01:10 -0500 Subject: [PATCH] add tests for hk including extensive cloak testing --- worlds/hk/test/__init__.py | 69 ++++++ worlds/hk/test/test_Settings.py | 235 ++++++++++++++++++++ worlds/hk/test/test_collect.py | 382 ++++++++++++++++++++++++++++++++ 3 files changed, 686 insertions(+) create mode 100644 worlds/hk/test/__init__.py create mode 100644 worlds/hk/test/test_Settings.py create mode 100644 worlds/hk/test/test_collect.py diff --git a/worlds/hk/test/__init__.py b/worlds/hk/test/__init__.py new file mode 100644 index 000000000000..d8369b752f79 --- /dev/null +++ b/worlds/hk/test/__init__.py @@ -0,0 +1,69 @@ +from test.bases import WorldTestBase +import typing +from BaseClasses import CollectionState # , Item, MultiWorld +from .. import HKWorld + + +class HKTestBase(WorldTestBase): + game = "Hollow Knight" + world: HKWorld + + def assertAccessIndependency( + self, + locations: typing.List[str], + possible_items: typing.Iterable[typing.Iterable[str]], + only_check_listed: bool = False) -> None: + """Asserts that the provided locations can't be reached without + the listed items but can be reached with any + one of the provided combinations""" + all_items = [ + item_name for + item_names in + possible_items for + item_name in + item_names + ] + + state = CollectionState(self.multiworld) + + for item_names in possible_items: + items = self.get_items_by_name(item_names) + for item in items: + self.collect_all_but(item.name) + for location in locations: + self.assertTrue(state.can_reach(location, "Location", 1), + f"{location} not reachable with {item_names}") + for item in items: + state.remove(item) + + def assertAccessWithout( + self, + locations: typing.List[str], + possible_items: typing.Iterable[typing.Iterable[str]]) -> None: + """Asserts that the provided locations can't be reached without the + listed items but can be reached with any + one of the provided combinations""" + all_items = [ + item_name for + item_names in + possible_items for + item_name in + item_names + ] + + state = CollectionState(self.multiworld) + self.collect_all_but(all_items, state) + for location in locations: + self.assertTrue( + state.can_reach(location, "Location", 1), + f"{location} is not reachable without {all_items}") + + +class selectSeedHK(WorldTestBase): + game = "Hollow Knight" + # player: typing.ClassVar[int] = 1 + seed = 0 + world: HKWorld + + def world_setup(self, *args, **kwargs): + super().world_setup(self.seed) diff --git a/worlds/hk/test/test_Settings.py b/worlds/hk/test/test_Settings.py new file mode 100644 index 000000000000..25b4b8e6b719 --- /dev/null +++ b/worlds/hk/test/test_Settings.py @@ -0,0 +1,235 @@ +from . import HKTestBase, selectSeedHK + + +class TestGoal_any(HKTestBase): + options = { + "Goal": "any", + } + + +class TestGoal_hollowknight(HKTestBase): + options = { + "Goal": "hollowknight", + } + + +class TestGoal_siblings(HKTestBase): + options = { + "Goal": "siblings", + } + + +class TestGoal_radiance(HKTestBase): + options = { + "Goal": "radiance", + } + + +class TestGoal_godhome(HKTestBase): + options = { + "Goal": "godhome", + } + + +class TestGoal_godhome_flower(HKTestBase): + options = { + "Goal": "godhome_flower", + } + + +class TestRandomize_All(HKTestBase): + options = { + "Goal": "any", + + "RandomizeDreamers": True, + "RandomizeSkills": True, + "RandomizeFocus": True, + "RandomizeSwim": True, + "RandomizeCharms": True, + "RandomizeKeys": True, + "RandomizeMaskShards": True, + "RandomizeVesselFragments": True, + "RandomizeCharmNotches": True, + "RandomizePaleOre": True, + "RandomizeGeoChests": True, + "RandomizeJunkPitChests": True, + "RandomizeRancidEggs": True, + "RandomizeRelics": True, + "RandomizeWhisperingRoots": True, + "RandomizeBossEssence": True, + "RandomizeGrubs": True, + "RandomizeMimics": True, + "RandomizeMaps": True, + "RandomizeStags": True, + "RandomizeLifebloodCocoons": True, + "RandomizeGrimmkinFlames": True, + "RandomizeJournalEntries": True, + "RandomizeNail": True, + "RandomizeGeoRocks": True, + "RandomizeBossGeo": True, + "RandomizeSoulTotems": True, + "RandomizeLoreTablets": True, + "RandomizeElevatorPass": True, + } + + +class TestRandomize_None(HKTestBase): + options = { + "Goal": "any", + + "RandomizeDreamers": False, + "RandomizeSkills": False, + "RandomizeFocus": False, + "RandomizeSwim": False, + "RandomizeCharms": False, + "RandomizeKeys": False, + "RandomizeMaskShards": False, + "RandomizeVesselFragments": False, + "RandomizeCharmNotches": False, + "RandomizePaleOre": False, + "RandomizeGeoChests": False, + "RandomizeJunkPitChests": False, + "RandomizeRancidEggs": False, + "RandomizeRelics": False, + "RandomizeWhisperingRoots": False, + "RandomizeBossEssence": False, + "RandomizeGrubs": False, + "RandomizeMimics": False, + "RandomizeMaps": False, + "RandomizeStags": False, + "RandomizeLifebloodCocoons": False, + "RandomizeGrimmkinFlames": False, + "RandomizeJournalEntries": False, + "RandomizeNail": False, + "RandomizeGeoRocks": False, + "RandomizeBossGeo": False, + "RandomizeSoulTotems": False, + "RandomizeLoreTablets": False, + "RandomizeElevatorPass": False, + } + + +class TestSplit_All(HKTestBase): + options = { + "Goal": "any", + + "SplitCrystalHeart": True, + "SplitMothwingCloak": True, + "SplitMantisClaw": True, + } + + +class TestCosts_All(HKTestBase): + options = { + "Goal": "any", + + "EggShopSlots": 9, + "SlyShopSlots": 9, + "SlyKeyShopSlots": 9, + "IseldaShopSlots": 9, + "SalubraShopSlots": 9, + "SalubraCharmShopSlots": 9, + "LegEaterShopSlots": 9, + "GrubfatherRewardSlots": 9, + "SeerRewardSlots": 9, + "ExtraShopSlots": 9, + "CostSanity": True, + "CostSanityHybridChance": 1, + "CostSanityEggWeight": 1, + "CostSanityGrubWeight": 1, + "CostSanityEssenceWeight": 1, + "CostSanityCharmWeight": 1, + "CostSanityGeoWeight": 1, + } + +# RandomizeDreamers, +# RandomizeSkills, +# RandomizeFocus, +# RandomizeSwim, +# RandomizeCharms, +# RandomizeKeys, +# RandomizeMaskShards, +# RandomizeVesselFragments, +# RandomizeCharmNotches, +# RandomizePaleOre, +# RandomizeGeoChests, +# RandomizeJunkPitChests, +# RandomizeRancidEggs, +# RandomizeRelics, +# RandomizeWhisperingRoots, +# RandomizeBossEssence, +# RandomizeGrubs, +# RandomizeMimics, +# RandomizeMaps, +# RandomizeStags, +# RandomizeLifebloodCocoons, +# RandomizeGrimmkinFlames, +# RandomizeJournalEntries, +# RandomizeNail, +# RandomizeGeoRocks, +# RandomizeBossGeo, +# RandomizeSoulTotems, +# RandomizeLoreTablets, +# RandomizeElevatorPass, +# StartLocation, +# Goal, +# WhitePalace, +# SplitCrystalHeart, +# SplitMothwingCloak, +# SplitMantisClaw, +# AddUnshuffledLocations, + + +# # not useful without more asserts +# PreciseMovement, +# ProficientCombat, +# BackgroundObjectPogos, +# EnemyPogos, +# ObscureSkips, +# ShadeSkips, +# InfectionSkips, +# FireballSkips, +# SpikeTunnels, +# AcidSkips, +# DamageBoosts, +# DangerousSkips, +# DarkRooms, +# ComplexSkips, +# DifficultSkips, +# RemoveSpellUpgrades, +# MinimumGeoPrice, +# MaximumGeoPrice, +# MinimumGrubPrice, +# MaximumGrubPrice, +# MinimumEssencePrice, +# MaximumEssencePrice, +# MinimumCharmPrice, +# MaximumCharmPrice, +# RandomCharmCosts, +# PlandoCharmCosts, +# MinimumEggPrice, +# MaximumEggPrice, +# EggShopSlots, +# SlyShopSlots, +# SlyKeyShopSlots, +# IseldaShopSlots, +# SalubraShopSlots, +# SalubraCharmShopSlots, +# LegEaterShopSlots, +# GrubfatherRewardSlots, +# SeerRewardSlots, +# ExtraShopSlots, +# CostSanity, +# CostSanityHybridChance, +# CostSanityEggWeight, +# CostSanityGrubWeight, +# CostSanityEssenceWeight, +# CostSanityCharmWeight, +# CostSanityGeoWeight, + +# # should be ok not testing +# StartingGeo, +# DeathLink, +# DeathLinkShade, +# DeathLinkBreaksFragileCharms, +# ExtraPlatforms, diff --git a/worlds/hk/test/test_collect.py b/worlds/hk/test/test_collect.py new file mode 100644 index 000000000000..a3c93f7c91b8 --- /dev/null +++ b/worlds/hk/test/test_collect.py @@ -0,0 +1,382 @@ +import unittest +from worlds.AutoWorld import AutoWorldRegister +from test.general import setup_solo_multiworld +from .. import HKWorld + + +class TestBase(unittest.TestCase): + def testCollect(self): + game_name, world_type = "Hollow Knight", HKWorld + multiworld = setup_solo_multiworld(world_type) + proxy_world = multiworld.worlds[1] + empty_state = multiworld.state.copy() + + for item_name in world_type.item_name_to_id: + with self.subTest("Create Item", item_name=item_name, game_name=game_name): + item = proxy_world.create_item(item_name) + + with self.subTest("Item Name", item_name=item_name, game_name=game_name): + self.assertEqual(item.name, item_name) + + if item.advancement: + with self.subTest("Item State Collect", item_name=item_name, game_name=game_name): + multiworld.state.collect(item, True) + + with self.subTest("Item State Remove", item_name=item_name, game_name=game_name): + multiworld.state.remove(item) + + self.assertEqual(multiworld.state.prog_items, empty_state.prog_items, + "Item Collect -> Remove should restore empty state.") + else: + with self.subTest("Item State Collect No Change", item_name=item_name, game_name=game_name): + # Non-Advancement should not modify state. + base_state = multiworld.state.prog_items.copy() + multiworld.state.collect(item) + self.assertEqual(base_state, multiworld.state.prog_items) + + multiworld.state.prog_items = empty_state.prog_items + + # def testCollect_split_cloak(self): + # game_name, world_type = "Hollow Knight", HKWorld + # multiworld = setup_solo_multiworld(world_type) + # proxy_world = multiworld.worlds[1] + # empty_state = multiworld.state.copy() + + # l_cloaks = ["Left_Mothwing_Cloak", "Right_Mothwing_Cloak", "Left_Mothwing_Cloak"] + # r_cloaks = ["Left_Mothwing_Cloak", "Right_Mothwing_Cloak", "Right_Mothwing_Cloak"] + # for cloaks in [l_cloaks, r_cloaks]: + # items = [] + # for item_name in cloaks: + # with self.subTest("Create Item", item_name=item_name, game_name=game_name): + # item = proxy_world.create_item(item_name) + # items.append(item) + + # with self.subTest("Item Name", item_name=item_name, game_name=game_name): + # self.assertEqual(item.name, item_name) + + # if item.advancement: + # with self.subTest("Item State Collect", item_name=item_name, game_name=game_name): + # multiworld.state.collect(item, True) + # proxy_world.random.shuffle(items) + # for item in items: + # with self.subTest("Item State Remove", item_name=item_name, game_name=game_name): + # multiworld.state.remove(item) + + # self.assertEqual(multiworld.state.prog_items, empty_state.prog_items, + # f"Item Collect -> Remove should restore empty state.\n{multiworld.state.prog_items}\n\n{empty_state.prog_items}") + + # multiworld.state.prog_items = empty_state.prog_items + + def cloak_test(self, collect_cloaks, remove_cloaks, final_state): + game_name, world_type = "Hollow Knight", HKWorld + multiworld = setup_solo_multiworld(world_type) + proxy_world = multiworld.worlds[1] + empty_state = multiworld.state.copy() + + items = [] + for item_name in collect_cloaks: + with self.subTest("Create Item", item_name=item_name, game_name=game_name): + item = proxy_world.create_item(item_name) + items.append(item) + + with self.subTest("Item State Collect", item_name=item_name, game_name=game_name): + multiworld.state.collect(item, True) + + for item_name in remove_cloaks: + index, item = next((index, item) for index, item in enumerate(items) if item.name == item_name) + items.pop(index) + with self.subTest("Item State Remove", item_name=item_name, game_name=game_name): + multiworld.state.remove(item) + + for item_name in ("Left_Mothwing_Cloak", "LEFTDASH", "Right_Mothwing_Cloak", "RIGHTDASH",): + if item_name in final_state: + self.assertEqual( + multiworld.state.prog_items[1][item_name], final_state[item_name], + f"expected {final_state[item_name]} {item_name}, found {multiworld.state.prog_items[1][item_name]}" + f"\nTest collected\n{collect_cloaks}\nand removed\n{remove_cloaks}\n" + ) + else: + self.assertEqual( + multiworld.state.prog_items[1][item_name], 0, + f"expected 0 {item_name}, found {multiworld.state.prog_items[1][item_name]}" + f"\nTest collected\n{collect_cloaks}\nand removed\n{remove_cloaks}\n" + ) + + multiworld.state.prog_items = empty_state.prog_items + + def testCollect_cloak_iterations(self): + # with self.subTest("Split Left Only"): + # self.cloak_test( + # ["Left_Mothwing_Cloak"], + # ["Left_Mothwing_Cloak"], + # {}, + # ) + + # with self.subTest("LRR - L"): + # self.cloak_test( + # ["Left_Mothwing_Cloak", "Right_Mothwing_Cloak", "Right_Mothwing_Cloak"], + # ["Left_Mothwing_Cloak"], + # {"Right_Mothwing_Cloak": 2, "RIGHTDASH": 2}, + # ) + + # with self.subTest("RLL - R"): + # self.cloak_test( + # ["Right_Mothwing_Cloak", "Left_Mothwing_Cloak", "Left_Mothwing_Cloak"], + # ["Right_Mothwing_Cloak"], + # {"Left_Mothwing_Cloak": 2, "LEFTDASH": 2}, + # ) + + # LLR + with self.subTest("L - L"): + self.cloak_test( + ["Left_Mothwing_Cloak"], + ["Left_Mothwing_Cloak"], + {}, + ) + with self.subTest("LL - L"): + self.cloak_test( + ["Left_Mothwing_Cloak", "Left_Mothwing_Cloak"], + ["Left_Mothwing_Cloak"], + {"Left_Mothwing_Cloak": 1, "LEFTDASH": 1}, + ) + with self.subTest("LLR - L"): + self.cloak_test( + ["Left_Mothwing_Cloak", "Left_Mothwing_Cloak", "Right_Mothwing_Cloak"], + ["Left_Mothwing_Cloak"], + {"Left_Mothwing_Cloak": 1, "LEFTDASH": 1, "Right_Mothwing_Cloak": 1, "RIGHTDASH": 1}, + ) + # with self.subTest("L - R"): + # self.cloak_test( + # ["Left_Mothwing_Cloak"], + # ["Right_Mothwing_Cloak"], + # {}, + # ) + # with self.subTest("LL - R"): + # self.cloak_test( + # ["Left_Mothwing_Cloak", "Left_Mothwing_Cloak"], + # ["Right_Mothwing_Cloak"], + # {}, + # ) + with self.subTest("LLR - R"): + self.cloak_test( + ["Left_Mothwing_Cloak", "Left_Mothwing_Cloak", "Right_Mothwing_Cloak"], + ["Right_Mothwing_Cloak"], + {"Left_Mothwing_Cloak": 2, "LEFTDASH": 2}, + ) + + # LRL + with self.subTest("L - L"): + self.cloak_test( + ["Left_Mothwing_Cloak"], + ["Left_Mothwing_Cloak"], + {}, + ) + with self.subTest("LR - L"): + self.cloak_test( + ["Left_Mothwing_Cloak", "Right_Mothwing_Cloak"], + ["Left_Mothwing_Cloak"], + {"Right_Mothwing_Cloak": 1, "RIGHTDASH": 1}, + ) + with self.subTest("LRL - L"): + self.cloak_test( + ["Left_Mothwing_Cloak", "Right_Mothwing_Cloak", "Left_Mothwing_Cloak"], + ["Left_Mothwing_Cloak"], + {"Left_Mothwing_Cloak": 1, "LEFTDASH": 1, "Right_Mothwing_Cloak": 1, "RIGHTDASH": 1}, + ) + # with self.subTest("L - R"): + # self.cloak_test( + # ["Left_Mothwing_Cloak"], + # ["Right_Mothwing_Cloak"], + # {}, + # ) + with self.subTest("LR - R"): + self.cloak_test( + ["Left_Mothwing_Cloak", "Right_Mothwing_Cloak"], + ["Right_Mothwing_Cloak"], + {"Left_Mothwing_Cloak": 1, "LEFTDASH": 1}, + ) + with self.subTest("LRL - R"): + self.cloak_test( + ["Left_Mothwing_Cloak", "Right_Mothwing_Cloak", "Left_Mothwing_Cloak"], + ["Right_Mothwing_Cloak"], + {"Left_Mothwing_Cloak": 2, "LEFTDASH": 2}, + ) + + # RLL + # with self.subTest("R - L"): + # self.cloak_test( + # ["Right_Mothwing_Cloak"], + # ["Left_Mothwing_Cloak"], + # {}, + # ) + with self.subTest("RL - L"): + self.cloak_test( + ["Right_Mothwing_Cloak", "Left_Mothwing_Cloak"], + ["Left_Mothwing_Cloak"], + {"Right_Mothwing_Cloak": 1, "RIGHTDASH": 1}, + ) + with self.subTest("RLL - L"): + self.cloak_test( + ["Right_Mothwing_Cloak", "Left_Mothwing_Cloak", "Left_Mothwing_Cloak"], + ["Left_Mothwing_Cloak"], + {"Left_Mothwing_Cloak": 1, "LEFTDASH": 1, "Right_Mothwing_Cloak": 1, "RIGHTDASH": 1}, + ) + with self.subTest("R - R"): + self.cloak_test( + ["Right_Mothwing_Cloak"], + ["Right_Mothwing_Cloak"], + {}, + ) + with self.subTest("RL - R"): + self.cloak_test( + ["Right_Mothwing_Cloak", "Left_Mothwing_Cloak"], + ["Right_Mothwing_Cloak"], + {"Left_Mothwing_Cloak": 1, "LEFTDASH": 1}, + ) + with self.subTest("RLL - R"): + self.cloak_test( + ["Right_Mothwing_Cloak", "Left_Mothwing_Cloak", "Left_Mothwing_Cloak"], + ["Right_Mothwing_Cloak"], + {"Left_Mothwing_Cloak": 2, "LEFTDASH": 2}, + ) + + # RRL + # with self.subTest("R - L"): + # self.cloak_test( + # ["Right_Mothwing_Cloak"], + # ["Left_Mothwing_Cloak"], + # {}, + # ) + # with self.subTest("RR - L"): + # self.cloak_test( + # ["Right_Mothwing_Cloak", "Right_Mothwing_Cloak"], + # ["Left_Mothwing_Cloak"], + # {"Right_Mothwing_Cloak": 1, "RIGHTDASH": 1}, + # ) + with self.subTest("RRL - L"): + self.cloak_test( + ["Right_Mothwing_Cloak", "Right_Mothwing_Cloak", "Left_Mothwing_Cloak"], + ["Left_Mothwing_Cloak"], + {"Right_Mothwing_Cloak": 2, "RIGHTDASH": 2}, + ) + with self.subTest("R - R"): + self.cloak_test( + ["Right_Mothwing_Cloak"], + ["Right_Mothwing_Cloak"], + {}, + ) + with self.subTest("RR - R"): + self.cloak_test( + ["Right_Mothwing_Cloak", "Right_Mothwing_Cloak"], + ["Right_Mothwing_Cloak"], + {"Right_Mothwing_Cloak": 1, "RIGHTDASH": 1}, + ) + with self.subTest("RRL - R"): + self.cloak_test( + ["Right_Mothwing_Cloak", "Right_Mothwing_Cloak", "Left_Mothwing_Cloak"], + ["Right_Mothwing_Cloak"], + {"Left_Mothwing_Cloak": 1, "LEFTDASH": 1, "Right_Mothwing_Cloak": 1, "RIGHTDASH": 1}, + ) + + # RLR + # with self.subTest("R - L"): + # self.cloak_test( + # ["Right_Mothwing_Cloak"], + # ["Left_Mothwing_Cloak"], + # {}, + # ) + with self.subTest("RL - L"): + self.cloak_test( + ["Right_Mothwing_Cloak", "Left_Mothwing_Cloak"], + ["Left_Mothwing_Cloak"], + {"Right_Mothwing_Cloak": 1, "RIGHTDASH": 1}, + ) + with self.subTest("RLR - L"): + self.cloak_test( + ["Right_Mothwing_Cloak", "Left_Mothwing_Cloak", "Right_Mothwing_Cloak"], + ["Left_Mothwing_Cloak"], + {"Right_Mothwing_Cloak": 2, "RIGHTDASH": 2}, + ) + with self.subTest("R - R"): + self.cloak_test( + ["Right_Mothwing_Cloak"], + ["Right_Mothwing_Cloak"], + {}, + ) + with self.subTest("RL - R"): + self.cloak_test( + ["Right_Mothwing_Cloak", "Left_Mothwing_Cloak"], + ["Right_Mothwing_Cloak"], + {"Left_Mothwing_Cloak": 1, "LEFTDASH": 1}, + ) + with self.subTest("RLR - R"): + self.cloak_test( + ["Right_Mothwing_Cloak", "Left_Mothwing_Cloak", "Right_Mothwing_Cloak"], + ["Right_Mothwing_Cloak"], + {"Left_Mothwing_Cloak": 1, "LEFTDASH": 1, "Right_Mothwing_Cloak": 1, "RIGHTDASH": 1}, + ) + + # LRR + with self.subTest("L - L"): + self.cloak_test( + ["Left_Mothwing_Cloak"], + ["Left_Mothwing_Cloak"], + {}, + ) + with self.subTest("LR - L"): + self.cloak_test( + ["Left_Mothwing_Cloak", "Right_Mothwing_Cloak"], + ["Left_Mothwing_Cloak"], + {"Right_Mothwing_Cloak": 1, "RIGHTDASH": 1}, + ) + with self.subTest("LRR - L"): + self.cloak_test( + ["Left_Mothwing_Cloak", "Right_Mothwing_Cloak", "Right_Mothwing_Cloak"], + ["Left_Mothwing_Cloak"], + {"Right_Mothwing_Cloak": 2, "RIGHTDASH": 2}, + ) + # with self.subTest("L - R"): + # self.cloak_test( + # ["Left_Mothwing_Cloak"], + # ["Right_Mothwing_Cloak"], + # {}, + # ) + with self.subTest("LR - R"): + self.cloak_test( + ["Left_Mothwing_Cloak", "Right_Mothwing_Cloak"], + ["Right_Mothwing_Cloak"], + {"Left_Mothwing_Cloak": 1, "LEFTDASH": 1}, + ) + with self.subTest("LRR - R"): + self.cloak_test( + ["Left_Mothwing_Cloak", "Right_Mothwing_Cloak", "Right_Mothwing_Cloak"], + ["Right_Mothwing_Cloak"], + {"Left_Mothwing_Cloak": 1, "LEFTDASH": 1, "Right_Mothwing_Cloak": 1, "RIGHTDASH": 1}, + ) + + # extra in pool + with self.subTest("Bonus: LLRL - L"): + self.cloak_test( + ["Left_Mothwing_Cloak", "Left_Mothwing_Cloak", "Right_Mothwing_Cloak", "Left_Mothwing_Cloak"], + ["Left_Mothwing_Cloak"], + {"Left_Mothwing_Cloak": 2, "LEFTDASH": 2, "Right_Mothwing_Cloak": 1, "RIGHTDASH": 2}, + ) + with self.subTest("Bonus: LLRL - R"): + self.cloak_test( + ["Left_Mothwing_Cloak", "Left_Mothwing_Cloak", "Right_Mothwing_Cloak", "Left_Mothwing_Cloak"], + ["Right_Mothwing_Cloak"], + {"Left_Mothwing_Cloak": 3, "LEFTDASH": 3}, + ) + + # non-split + with self.subTest("Bonus: non-split"): + self.cloak_test( + ["Mothwing_Cloak", "Shade_Cloak"], + ["Mothwing_Cloak"], + {"Shade_Cloak": 1, "LEFTDASH": 1, "RIGHTDASH": 1}, + ) + self.cloak_test( + ["Mothwing_Cloak", "Shade_Cloak"], + ["Shade_Cloak"], + {"Mothwing_Cloak": 1, "LEFTDASH": 1, "RIGHTDASH": 1}, + )