diff --git a/src/test/java/com/diamonddagger590/mccore/builder/item/ItemPluginTypeGetCustomItemTest.java b/src/test/java/com/diamonddagger590/mccore/builder/item/ItemPluginTypeGetCustomItemTest.java new file mode 100644 index 0000000..856c45a --- /dev/null +++ b/src/test/java/com/diamonddagger590/mccore/builder/item/ItemPluginTypeGetCustomItemTest.java @@ -0,0 +1,319 @@ +package com.diamonddagger590.mccore.builder.item; + +import com.diamonddagger590.mccore.external.itemsadder.CoreItemsAdderHook; +import com.diamonddagger590.mccore.external.nexo.CoreNexoHook; +import com.diamonddagger590.mccore.registry.RegistryAccess; +import com.diamonddagger590.mccore.registry.RegistryKey; +import com.diamonddagger590.mccore.registry.plugin.PluginHook; +import com.diamonddagger590.mccore.registry.plugin.PluginHookRegistry; +import com.diamonddagger590.mccore.testing.CorePluginTestHelper; +import com.diamonddagger590.mccore.testing.RegistryResetExtension; +import com.diamonddagger590.mccore.util.item.CustomItemWrapper; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.MockBukkit; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Tests for {@link ItemPluginType#getCustomItem(String)} lambdas across all enum constants. + */ +class ItemPluginTypeGetCustomItemTest { + + @BeforeEach + void setUp() { + MockBukkit.mock(); + RegistryResetExtension.setupRegistry(); + CorePluginTestHelper.installMinimalInstance(); + } + + @AfterEach + void tearDown() { + CorePluginTestHelper.uninstallInstance(); + RegistryResetExtension.resetRegistry(); + MockBukkit.unmock(); + } + + @SuppressWarnings("unchecked") + private void registerHookWithKey(PluginHook hook, Class keyClass) throws Exception { + PluginHookRegistry registry = RegistryAccess.registryAccess().registry(RegistryKey.PLUGIN_HOOK); + Field hooksField = PluginHookRegistry.class.getDeclaredField("hooks"); + hooksField.setAccessible(true); + Map, PluginHook> hooks = (Map, PluginHook>) hooksField.get(registry); + hooks.put(keyClass, hook); + } + + private void registerNexoHook(StubNexoHook hook) throws Exception { + registerHookWithKey(hook, CoreNexoHook.class); + } + + private void registerItemsAdderHook(StubItemsAdderHook hook) throws Exception { + registerHookWithKey(hook, CoreItemsAdderHook.class); + } + + static class StubNexoHook extends CoreNexoHook { + + private final String recognizedItem; + private final ItemStack returnedItem; + + StubNexoHook(String recognizedItem, ItemStack returnedItem) { + super(null); + this.recognizedItem = recognizedItem; + this.returnedItem = returnedItem; + } + + @Override + public boolean isItem(@NotNull String itemName) { + return recognizedItem != null && recognizedItem.equals(itemName); + } + + @Override + public boolean isItem(@NotNull ItemStack itemStack) { + return false; + } + + @NotNull + @Override + public Optional item(@NotNull String itemName) { + if (recognizedItem != null && recognizedItem.equals(itemName) && returnedItem != null) { + return Optional.of(returnedItem); + } + return Optional.empty(); + } + + @NotNull + @Override + public Optional> itemModels(@NotNull ItemStack itemStack) { + return Optional.empty(); + } + + @NotNull + @Override + public String itemName(@NotNull CustomItemWrapper customItemWrapper) { + return "Test Item"; + } + } + + static class StubItemsAdderHook extends CoreItemsAdderHook { + + private final String recognizedItem; + private final ItemStack returnedItem; + + StubItemsAdderHook(String recognizedItem, ItemStack returnedItem) { + super(null); + this.recognizedItem = recognizedItem; + this.returnedItem = returnedItem; + } + + @Override + public boolean isItem(@NotNull String itemName) { + return recognizedItem != null && recognizedItem.equals(itemName); + } + + @Override + public boolean isItem(@NotNull ItemStack itemStack) { + return false; + } + + @NotNull + @Override + public Optional item(@NotNull String itemName) { + if (recognizedItem != null && recognizedItem.equals(itemName) && returnedItem != null) { + return Optional.of(returnedItem); + } + return Optional.empty(); + } + + @NotNull + @Override + public Optional> itemModels(@NotNull ItemStack itemStack) { + return Optional.empty(); + } + + @NotNull + @Override + public String itemName(@NotNull CustomItemWrapper customItemWrapper) { + return "Test Item"; + } + } + + @Nested + @DisplayName("NEXO getCustomItem") + class NexoGetCustomItem { + + @Test + @DisplayName("Given Nexo hook present and item found, when getting custom item, then returns hook item") + void getCustomItem_returnsHookItem_whenNexoHookPresentAndItemFound() throws Exception { + ItemStack expected = new ItemStack(Material.DIAMOND_SWORD); + registerNexoHook(new StubNexoHook("nexo:custom_sword", expected)); + + ItemStack result = ItemPluginType.NEXO.getCustomItem("nexo:custom_sword"); + assertNotNull(result); + assertEquals(Material.DIAMOND_SWORD, result.getType()); + } + + @Test + @DisplayName("Given Nexo hook present but item not found, when getting custom item with vanilla name, then falls back to vanilla ItemType") + void getCustomItem_fallsBackToVanilla_whenNexoHookPresentButItemNotFound() throws Exception { + registerNexoHook(new StubNexoHook("nexo:other", null)); + + ItemStack result = ItemPluginType.NEXO.getCustomItem("stone"); + assertNotNull(result); + assertEquals(Material.STONE, result.getType()); + } + + @Test + @DisplayName("Given no Nexo hook and vanilla item name, when getting custom item, then returns vanilla item") + void getCustomItem_returnsVanillaItem_whenNoHookAndVanillaName() { + ItemStack result = ItemPluginType.NEXO.getCustomItem("diamond"); + assertNotNull(result); + assertEquals(Material.DIAMOND, result.getType()); + } + + @Test + @DisplayName("Given no Nexo hook and invalid item name, when getting custom item, then returns stone fallback") + void getCustomItem_returnsStoneFallback_whenNoHookAndInvalidName() { + ItemStack result = ItemPluginType.NEXO.getCustomItem("completely_invalid_item_xyz_12345"); + assertNotNull(result); + assertEquals(Material.STONE, result.getType()); + } + } + + @Nested + @DisplayName("ITEMS_ADDER getCustomItem") + class ItemsAdderGetCustomItem { + + @Test + @DisplayName("Given ItemsAdder hook present and item found, when getting custom item, then returns hook item") + void getCustomItem_returnsHookItem_whenItemsAdderHookPresentAndItemFound() throws Exception { + ItemStack expected = new ItemStack(Material.IRON_SWORD); + registerItemsAdderHook(new StubItemsAdderHook("ia:custom_sword", expected)); + + ItemStack result = ItemPluginType.ITEMS_ADDER.getCustomItem("ia:custom_sword"); + assertNotNull(result); + assertEquals(Material.IRON_SWORD, result.getType()); + } + + @Test + @DisplayName("Given ItemsAdder hook present but item not found, when getting custom item with vanilla name, then falls back to vanilla ItemType") + void getCustomItem_fallsBackToVanilla_whenItemsAdderHookPresentButItemNotFound() throws Exception { + registerItemsAdderHook(new StubItemsAdderHook("ia:other", null)); + + ItemStack result = ItemPluginType.ITEMS_ADDER.getCustomItem("iron_ingot"); + assertNotNull(result); + assertEquals(Material.IRON_INGOT, result.getType()); + } + + @Test + @DisplayName("Given no ItemsAdder hook and vanilla item name, when getting custom item, then returns vanilla item") + void getCustomItem_returnsVanillaItem_whenNoHookAndVanillaName() { + ItemStack result = ItemPluginType.ITEMS_ADDER.getCustomItem("gold_ingot"); + assertNotNull(result); + assertEquals(Material.GOLD_INGOT, result.getType()); + } + + @Test + @DisplayName("Given no ItemsAdder hook and invalid item name, when getting custom item, then returns stone fallback") + void getCustomItem_returnsStoneFallback_whenNoHookAndInvalidName() { + ItemStack result = ItemPluginType.ITEMS_ADDER.getCustomItem("completely_invalid_item_xyz_12345"); + assertNotNull(result); + assertEquals(Material.STONE, result.getType()); + } + } + + @Nested + @DisplayName("NONE getCustomItem") + class NoneGetCustomItem { + + @Test + @DisplayName("Given Nexo hook present and isItem true and item found, when getting custom item, then returns Nexo hook item") + void getCustomItem_returnsNexoItem_whenNexoRecognizesItem() throws Exception { + ItemStack expected = new ItemStack(Material.EMERALD); + registerNexoHook(new StubNexoHook("nexo:emerald_gem", expected)); + + ItemStack result = ItemPluginType.NONE.getCustomItem("nexo:emerald_gem"); + assertNotNull(result); + assertEquals(Material.EMERALD, result.getType()); + } + + @Test + @DisplayName("Given ItemsAdder hook present and isItem true and item found, when getting custom item, then returns ItemsAdder hook item") + void getCustomItem_returnsItemsAdderItem_whenItemsAdderRecognizesItem() throws Exception { + ItemStack expected = new ItemStack(Material.GOLD_INGOT); + registerItemsAdderHook(new StubItemsAdderHook("ia:gold_bar", expected)); + + ItemStack result = ItemPluginType.NONE.getCustomItem("ia:gold_bar"); + assertNotNull(result); + assertEquals(Material.GOLD_INGOT, result.getType()); + } + + @Test + @DisplayName("Given both hooks present but neither recognizes item, when getting custom item with vanilla name, then returns vanilla item") + void getCustomItem_returnsVanillaItem_whenNoHookRecognizesItem() throws Exception { + registerNexoHook(new StubNexoHook("nexo:other", null)); + registerItemsAdderHook(new StubItemsAdderHook("ia:other", null)); + + ItemStack result = ItemPluginType.NONE.getCustomItem("oak_planks"); + assertNotNull(result); + assertEquals(Material.OAK_PLANKS, result.getType()); + } + + @Test + @DisplayName("Given no hooks and vanilla item name, when getting custom item, then returns vanilla item") + void getCustomItem_returnsVanillaItem_whenNoHooksRegistered() { + ItemStack result = ItemPluginType.NONE.getCustomItem("cobblestone"); + assertNotNull(result); + assertEquals(Material.COBBLESTONE, result.getType()); + } + + @Test + @DisplayName("Given no hooks and invalid item name, when getting custom item, then returns stone fallback") + void getCustomItem_returnsStoneFallback_whenNoHooksAndInvalidName() { + ItemStack result = ItemPluginType.NONE.getCustomItem("completely_invalid_item_xyz_12345"); + assertNotNull(result); + assertEquals(Material.STONE, result.getType()); + } + + @Test + @DisplayName("Given Nexo hook recognizes item but item() returns empty, when getting custom item with vanilla name, then falls through to vanilla") + void getCustomItem_fallsThrough_whenNexoRecognizesButItemIsEmpty() throws Exception { + registerNexoHook(new StubNexoHook("stone", null) { + @Override + public boolean isItem(@NotNull String itemName) { + return "stone".equals(itemName); + } + }); + + ItemStack result = ItemPluginType.NONE.getCustomItem("stone"); + assertNotNull(result); + assertEquals(Material.STONE, result.getType()); + } + + @Test + @DisplayName("Given ItemsAdder hook recognizes item but item() returns empty, when getting custom item with vanilla name, then falls through to vanilla") + void getCustomItem_fallsThrough_whenItemsAdderRecognizesButItemIsEmpty() throws Exception { + registerItemsAdderHook(new StubItemsAdderHook("iron_ingot", null) { + @Override + public boolean isItem(@NotNull String itemName) { + return "iron_ingot".equals(itemName); + } + }); + + ItemStack result = ItemPluginType.NONE.getCustomItem("iron_ingot"); + assertNotNull(result); + assertEquals(Material.IRON_INGOT, result.getType()); + } + } +} diff --git a/src/test/java/com/diamonddagger590/mccore/util/item/CustomBlockWrapperTest.java b/src/test/java/com/diamonddagger590/mccore/util/item/CustomBlockWrapperTest.java index 37dc020..0d7caf1 100644 --- a/src/test/java/com/diamonddagger590/mccore/util/item/CustomBlockWrapperTest.java +++ b/src/test/java/com/diamonddagger590/mccore/util/item/CustomBlockWrapperTest.java @@ -2,9 +2,11 @@ import com.diamonddagger590.mccore.CorePlugin; import com.diamonddagger590.mccore.external.common.CustomBlockHook; +import com.diamonddagger590.mccore.external.common.CustomItemHook; import com.diamonddagger590.mccore.registry.RegistryAccess; import com.diamonddagger590.mccore.registry.RegistryKey; import com.diamonddagger590.mccore.registry.plugin.PluginHook; +import com.diamonddagger590.mccore.testing.CorePluginTestHelper; import com.diamonddagger590.mccore.testing.RegistryResetExtension; import org.bukkit.Location; import org.bukkit.Material; @@ -18,6 +20,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockito.Mockito; import java.lang.reflect.Field; import java.util.List; @@ -27,18 +31,23 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; class CustomBlockWrapperTest { @BeforeEach void setUp() { + MockBukkit.mock(); RegistryResetExtension.setupRegistry(); + CorePluginTestHelper.installMinimalInstance(); } @AfterEach void tearDown() { + CorePluginTestHelper.uninstallInstance(); RegistryResetExtension.resetRegistry(); + MockBukkit.unmock(); } private static CustomBlockWrapper materialWrapper(Material material) { @@ -58,13 +67,24 @@ private static CustomBlockWrapper customBlockWrapper(String customBlock) throws static class TestCustomBlockPluginHook extends PluginHook implements CustomBlockHook { + private final boolean recognizesBlock; + private final Set models; + private final List drops; + TestCustomBlockPluginHook() { + this(false, Set.of(), List.of()); + } + + TestCustomBlockPluginHook(boolean recognizesBlock, Set models, List drops) { super(null); + this.recognizesBlock = recognizesBlock; + this.models = models; + this.drops = drops; } @Override public boolean isCustomBlock(@NotNull Block block) { - return false; + return recognizesBlock; } @Override @@ -74,7 +94,7 @@ public boolean isCustomBlock(@NotNull String customBlock) { @Override public boolean isCustomBlockOfType(@NotNull Block block, @NotNull String customBlockType) { - return false; + return recognizesBlock && "nexo:ruby_ore".equals(customBlockType); } @Override @@ -84,7 +104,7 @@ public void placeCustomBlock(@NotNull Location location, @NotNull String blockId @NotNull @Override public List drops(@NotNull Block block, @NotNull ItemStack itemToBreakWith, @Nullable Entity entityBreaking) { - return List.of(); + return drops; } @Override @@ -98,7 +118,7 @@ public void removeBlock(@NotNull Block block) { @NotNull @Override public Optional> blockModels(@NotNull Block block) { - return Optional.empty(); + return models.isEmpty() ? Optional.empty() : Optional.of(models); } @NotNull @@ -108,6 +128,54 @@ public String blockName(@NotNull CustomBlockWrapper customBlockWrapper) { } } + static class TestCustomItemPluginHook extends PluginHook implements CustomItemHook { + + private final String recognizedItem; + private final ItemStack returnedItem; + + TestCustomItemPluginHook(String recognizedItem, ItemStack returnedItem) { + super(null); + this.recognizedItem = recognizedItem; + this.returnedItem = returnedItem; + } + + @Override + public boolean isItem(@NotNull String itemName) { + return recognizedItem != null && recognizedItem.equals(itemName); + } + + @Override + public boolean isItem(@NotNull ItemStack itemStack) { + return false; + } + + @Override + public boolean isItemOfType(@NotNull ItemStack itemStack, @NotNull String itemName) { + return false; + } + + @NotNull + @Override + public Optional item(@NotNull String itemName) { + if (recognizedItem != null && recognizedItem.equals(itemName) && returnedItem != null) { + return Optional.of(returnedItem); + } + return Optional.empty(); + } + + @NotNull + @Override + public Optional> itemModels(@NotNull ItemStack itemStack) { + return Optional.empty(); + } + + @NotNull + @Override + public String itemName(@NotNull CustomItemWrapper customItemWrapper) { + return "Test Item"; + } + } + @Nested @DisplayName("Material constructor") class MaterialConstructor { @@ -397,4 +465,214 @@ void toString_containsCustomBlockId_whenCustomBlockWrapper() throws Exception { assertTrue(result.contains("material=null"), "Expected material=null, got: " + result); } } + + @Nested + @DisplayName("blockName - vanilla material path") + class BlockNameVanilla { + + @Test + @DisplayName("Given material wrapper, when getting blockName, then returns lang tag with translation key") + void blockName_returnsLangTag_whenMaterialWrapper() { + CustomBlockWrapper wrapper = materialWrapper(Material.IRON_ORE); + String result = wrapper.blockName(); + assertTrue(result.startsWith(""), "Expected lang tag to end with >, got: " + result); + assertTrue(result.contains(Material.IRON_ORE.translationKey()), "Expected translation key, got: " + result); + } + } + + @Nested + @DisplayName("itemBuilder") + class ItemBuilderTests { + + @Test + @DisplayName("Given material wrapper, when getting itemBuilder, then returns builder with that material") + void itemBuilder_returnsMaterialBuilder_whenMaterialWrapper() { + CustomBlockWrapper wrapper = materialWrapper(Material.DIAMOND_ORE); + var builder = wrapper.itemBuilder(); + assertNotNull(builder); + } + + @Test + @DisplayName("Given custom block wrapper with no hooks, when getting itemBuilder, then returns AIR builder") + void itemBuilder_returnsAirBuilder_whenNoHooksRegistered() throws Exception { + CustomBlockWrapper wrapper = customBlockWrapper("nexo:unknown_block"); + var builder = wrapper.itemBuilder(); + assertNotNull(builder); + } + + @Test + @DisplayName("Given custom block wrapper with hook providing item, when getting itemBuilder, then returns hook item builder") + void itemBuilder_returnsHookItemBuilder_whenHookProvidesItem() throws Exception { + RegistryAccess.registryAccess().registry(RegistryKey.PLUGIN_HOOK) + .register(new TestCustomItemPluginHook("nexo:ruby_ore", new ItemStack(Material.EMERALD))); + CustomBlockWrapper wrapper = customBlockWrapper("nexo:ruby_ore"); + var builder = wrapper.itemBuilder(); + assertNotNull(builder); + } + + @Test + @DisplayName("Given custom block wrapper with hook not providing item, when getting itemBuilder, then returns AIR builder") + void itemBuilder_returnsAirBuilder_whenHookDoesNotProvideItem() throws Exception { + RegistryAccess.registryAccess().registry(RegistryKey.PLUGIN_HOOK) + .register(new TestCustomItemPluginHook("nexo:other_item", new ItemStack(Material.EMERALD))); + CustomBlockWrapper wrapper = customBlockWrapper("nexo:ruby_ore"); + var builder = wrapper.itemBuilder(); + assertNotNull(builder); + } + } + + @Nested + @DisplayName("equals(Block)") + class EqualsBlock { + + @Test + @DisplayName("Given material wrapper matching block type, when comparing with block, then returns true") + void equals_returnsTrue_whenMaterialMatchesBlockType() { + CustomBlockWrapper wrapper = materialWrapper(Material.STONE); + Block block = Mockito.mock(Block.class); + Mockito.when(block.getType()).thenReturn(Material.STONE); + assertTrue(wrapper.equals(block)); + } + + @Test + @DisplayName("Given material wrapper not matching block type, when comparing with block, then returns false") + void equals_returnsFalse_whenMaterialDoesNotMatchBlockType() { + CustomBlockWrapper wrapper = materialWrapper(Material.STONE); + Block block = Mockito.mock(Block.class); + Mockito.when(block.getType()).thenReturn(Material.DIRT); + assertFalse(wrapper.equals(block)); + } + + @Test + @DisplayName("Given custom block wrapper with hook confirming type, when comparing with block, then returns true") + void equals_returnsTrue_whenHookConfirmsCustomBlockType() throws Exception { + RegistryAccess.registryAccess().registry(RegistryKey.PLUGIN_HOOK) + .register(new TestCustomBlockPluginHook(true, Set.of("nexo:ruby_ore"), List.of())); + CustomBlockWrapper wrapper = customBlockWrapper("nexo:ruby_ore"); + Block block = Mockito.mock(Block.class); + assertTrue(wrapper.equals(block)); + } + + @Test + @DisplayName("Given custom block wrapper with no hooks, when comparing with block, then returns false") + void equals_returnsFalse_whenNoHooksForCustomBlock() throws Exception { + CustomBlockWrapper wrapper = customBlockWrapper("nexo:ruby_ore"); + Block block = Mockito.mock(Block.class); + assertFalse(wrapper.equals(block)); + } + } + + @Nested + @DisplayName("Block constructor") + class BlockConstructor { + + @Test + @DisplayName("Given block with custom hook recognizing it, when constructing wrapper, then sets custom block") + void constructor_setsCustomBlock_whenHookRecognizesBlock() { + RegistryAccess.registryAccess().registry(RegistryKey.PLUGIN_HOOK) + .register(new TestCustomBlockPluginHook(true, Set.of("nexo:ruby_ore"), List.of())); + Block block = Mockito.mock(Block.class); + Mockito.when(block.getType()).thenReturn(Material.STONE); + + CustomBlockWrapper wrapper = new CustomBlockWrapper(block); + assertTrue(wrapper.customBlock().isPresent()); + assertEquals("nexo:ruby_ore", wrapper.customBlock().get()); + assertFalse(wrapper.material().isPresent()); + } + + @Test + @DisplayName("Given block with no hooks, when constructing wrapper, then sets material from block type") + void constructor_setsMaterial_whenNoHooksRecognizeBlock() { + Block block = Mockito.mock(Block.class); + Mockito.when(block.getType()).thenReturn(Material.DIAMOND_ORE); + + CustomBlockWrapper wrapper = new CustomBlockWrapper(block); + assertTrue(wrapper.material().isPresent()); + assertEquals(Material.DIAMOND_ORE, wrapper.material().get()); + assertFalse(wrapper.customBlock().isPresent()); + } + + @Test + @DisplayName("Given block with hook that recognizes but returns empty models, when constructing wrapper, then sets material") + void constructor_setsMaterial_whenHookReturnsEmptyModels() { + RegistryAccess.registryAccess().registry(RegistryKey.PLUGIN_HOOK) + .register(new TestCustomBlockPluginHook(true, Set.of(), List.of())); + Block block = Mockito.mock(Block.class); + Mockito.when(block.getType()).thenReturn(Material.STONE); + + CustomBlockWrapper wrapper = new CustomBlockWrapper(block); + assertTrue(wrapper.material().isPresent()); + assertEquals(Material.STONE, wrapper.material().get()); + } + } + + @Nested + @DisplayName("static customModels") + class CustomModels { + + @Test + @DisplayName("Given block with no hooks registered, when getting customModels, then returns empty set") + void customModels_returnsEmptySet_whenNoHooksRegistered() { + Block block = Mockito.mock(Block.class); + Optional> result = CustomBlockWrapper.customModels(block); + assertTrue(result.isPresent()); + assertTrue(result.get().isEmpty()); + } + + @Test + @DisplayName("Given block with hook providing models, when getting customModels, then returns those models") + void customModels_returnsModels_whenHookProvidesModels() { + RegistryAccess.registryAccess().registry(RegistryKey.PLUGIN_HOOK) + .register(new TestCustomBlockPluginHook(true, Set.of("nexo:ruby_ore", "nexo:sapphire_ore"), List.of())); + Block block = Mockito.mock(Block.class); + Optional> result = CustomBlockWrapper.customModels(block); + assertTrue(result.isPresent()); + assertEquals(2, result.get().size()); + assertTrue(result.get().contains("nexo:ruby_ore")); + assertTrue(result.get().contains("nexo:sapphire_ore")); + } + } + + @Nested + @DisplayName("static drops") + class Drops { + + @Test + @DisplayName("Given block with hook registered, when getting drops, then returns hook drops") + void drops_returnsHookDrops_whenHookRegistered() { + ItemStack drop = new ItemStack(Material.DIAMOND); + RegistryAccess.registryAccess().registry(RegistryKey.PLUGIN_HOOK) + .register(new TestCustomBlockPluginHook(true, Set.of("nexo:ruby_ore"), List.of(drop))); + Block block = Mockito.mock(Block.class); + ItemStack tool = new ItemStack(Material.DIAMOND_PICKAXE); + + List result = CustomBlockWrapper.drops(block, tool, null); + assertEquals(1, result.size()); + assertEquals(Material.DIAMOND, result.get(0).getType()); + } + } + + @Nested + @DisplayName("static removeBlock") + class RemoveBlock { + + @Test + @DisplayName("Given block with no hooks, when removing block, then sets type to AIR") + void removeBlock_setsAir_whenNoHooksRegistered() { + Block block = Mockito.mock(Block.class); + CustomBlockWrapper.removeBlock(block); + Mockito.verify(block).setType(Material.AIR); + } + + @Test + @DisplayName("Given block with hook registered, when removing block, then delegates to hook") + void removeBlock_delegatesToHook_whenHookRegistered() { + TestCustomBlockPluginHook hook = Mockito.spy(new TestCustomBlockPluginHook(true, Set.of("nexo:ruby_ore"), List.of())); + RegistryAccess.registryAccess().registry(RegistryKey.PLUGIN_HOOK).register(hook); + Block block = Mockito.mock(Block.class); + CustomBlockWrapper.removeBlock(block); + Mockito.verify(hook).removeBlock(block); + } + } } diff --git a/src/test/java/com/diamonddagger590/mccore/util/item/CustomEntityWrapperTest.java b/src/test/java/com/diamonddagger590/mccore/util/item/CustomEntityWrapperTest.java index 0bad89d..7954011 100644 --- a/src/test/java/com/diamonddagger590/mccore/util/item/CustomEntityWrapperTest.java +++ b/src/test/java/com/diamonddagger590/mccore/util/item/CustomEntityWrapperTest.java @@ -14,6 +14,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockito.Mockito; import java.lang.reflect.Field; import java.util.Optional; @@ -23,18 +25,21 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; class CustomEntityWrapperTest { @BeforeEach void setUp() { + MockBukkit.mock(); RegistryResetExtension.setupRegistry(); } @AfterEach void tearDown() { RegistryResetExtension.resetRegistry(); + MockBukkit.unmock(); } private static CustomEntityWrapper entityTypeWrapper(EntityType entityType) { @@ -54,13 +59,29 @@ private static CustomEntityWrapper customEntityWrapper(String customEntity) thro static class TestCustomEntityPluginHook extends PluginHook implements CustomEntityHook { + private final boolean recognizesEntity; + private final Set models; + private final boolean confirmsType; + TestCustomEntityPluginHook() { + this(false, Set.of(), false); + } + + TestCustomEntityPluginHook(boolean recognizesEntity, Set models, boolean confirmsType) { super(null); + this.recognizesEntity = recognizesEntity; + this.models = models; + this.confirmsType = confirmsType; + } + + @Override + public boolean isCustomEntity(@NotNull Entity entity) { + return recognizesEntity; } @Override public boolean isCustomEntity(@NotNull UUID uuid) { - return false; + return recognizesEntity; } @Override @@ -70,13 +91,18 @@ public boolean isCustomEntity(@NotNull String customEntity) { @Override public boolean isCustomEntityOfType(@NotNull UUID uuid, @NotNull String customEntityType) { - return false; + return confirmsType && "mythicmobs:fire_dragon".equals(customEntityType); + } + + @Override + public boolean isCustomEntityOfType(@NotNull Entity entity, @NotNull String customEntityType) { + return confirmsType && "mythicmobs:fire_dragon".equals(customEntityType); } @NotNull @Override public Optional> entityModels(@NotNull Entity entity) { - return Optional.empty(); + return models.isEmpty() ? Optional.empty() : Optional.of(models); } @NotNull @@ -354,4 +380,131 @@ void entityName_returnsHookProvidedName_whenHookIsRegistered() throws Exception assertEquals("Fire Dragon", wrapper.entityName()); } } + + @Nested + @DisplayName("entityName - vanilla entity type path") + class EntityNameVanilla { + + @Test + @DisplayName("Given entity type wrapper, when getting entityName, then returns lang tag with translation key") + void entityName_returnsLangTag_whenEntityTypeWrapper() { + CustomEntityWrapper wrapper = entityTypeWrapper(EntityType.ZOMBIE); + String result = wrapper.entityName(); + assertTrue(result.startsWith(""), "Expected lang tag to end with >, got: " + result); + assertTrue(result.contains(EntityType.ZOMBIE.translationKey()), "Expected translation key, got: " + result); + } + } + + @Nested + @DisplayName("equals(Entity)") + class EqualsEntity { + + @Test + @DisplayName("Given entity type wrapper matching entity type, when comparing with entity, then returns true") + void equals_returnsTrue_whenEntityTypeMatchesEntityType() { + CustomEntityWrapper wrapper = entityTypeWrapper(EntityType.ZOMBIE); + Entity entity = Mockito.mock(Entity.class); + Mockito.when(entity.getType()).thenReturn(EntityType.ZOMBIE); + assertTrue(wrapper.equals(entity)); + } + + @Test + @DisplayName("Given entity type wrapper not matching entity type, when comparing with entity, then returns false") + void equals_returnsFalse_whenEntityTypeDoesNotMatchEntityType() { + CustomEntityWrapper wrapper = entityTypeWrapper(EntityType.ZOMBIE); + Entity entity = Mockito.mock(Entity.class); + Mockito.when(entity.getType()).thenReturn(EntityType.SKELETON); + assertFalse(wrapper.equals(entity)); + } + + @Test + @DisplayName("Given custom entity wrapper with hook confirming type, when comparing with entity, then returns true") + void equals_returnsTrue_whenHookConfirmsCustomEntityType() throws Exception { + RegistryAccess.registryAccess().registry(RegistryKey.PLUGIN_HOOK) + .register(new TestCustomEntityPluginHook(true, Set.of("mythicmobs:fire_dragon"), true)); + CustomEntityWrapper wrapper = customEntityWrapper("mythicmobs:fire_dragon"); + Entity entity = Mockito.mock(Entity.class); + assertTrue(wrapper.equals(entity)); + } + + @Test + @DisplayName("Given custom entity wrapper with no hooks, when comparing with entity, then returns false") + void equals_returnsFalse_whenNoHooksForCustomEntity() throws Exception { + CustomEntityWrapper wrapper = customEntityWrapper("mythicmobs:fire_dragon"); + Entity entity = Mockito.mock(Entity.class); + assertFalse(wrapper.equals(entity)); + } + } + + @Nested + @DisplayName("Entity constructor") + class EntityConstructor { + + @Test + @DisplayName("Given entity with hook recognizing it, when constructing wrapper, then sets custom entity") + void constructor_setsCustomEntity_whenHookRecognizesEntity() { + RegistryAccess.registryAccess().registry(RegistryKey.PLUGIN_HOOK) + .register(new TestCustomEntityPluginHook(true, Set.of("mythicmobs:fire_dragon"), false)); + Entity entity = Mockito.mock(Entity.class); + Mockito.when(entity.getType()).thenReturn(EntityType.ZOMBIE); + + CustomEntityWrapper wrapper = new CustomEntityWrapper(entity); + assertTrue(wrapper.customEntity().isPresent()); + assertEquals("mythicmobs:fire_dragon", wrapper.customEntity().get()); + assertFalse(wrapper.entityType().isPresent()); + } + + @Test + @DisplayName("Given entity with no hooks, when constructing wrapper, then sets entity type from entity") + void constructor_setsEntityType_whenNoHooksRecognizeEntity() { + Entity entity = Mockito.mock(Entity.class); + Mockito.when(entity.getType()).thenReturn(EntityType.CREEPER); + + CustomEntityWrapper wrapper = new CustomEntityWrapper(entity); + assertTrue(wrapper.entityType().isPresent()); + assertEquals(EntityType.CREEPER, wrapper.entityType().get()); + assertFalse(wrapper.customEntity().isPresent()); + } + + @Test + @DisplayName("Given entity with hook that recognizes but returns empty models, when constructing wrapper, then sets entity type") + void constructor_setsEntityType_whenHookReturnsEmptyModels() { + RegistryAccess.registryAccess().registry(RegistryKey.PLUGIN_HOOK) + .register(new TestCustomEntityPluginHook(true, Set.of(), false)); + Entity entity = Mockito.mock(Entity.class); + Mockito.when(entity.getType()).thenReturn(EntityType.ZOMBIE); + + CustomEntityWrapper wrapper = new CustomEntityWrapper(entity); + assertTrue(wrapper.entityType().isPresent()); + assertEquals(EntityType.ZOMBIE, wrapper.entityType().get()); + } + } + + @Nested + @DisplayName("static customModels") + class CustomModelsTests { + + @Test + @DisplayName("Given entity with no hooks registered, when getting customModels, then returns empty set") + void customModels_returnsEmptySet_whenNoHooksRegistered() { + Entity entity = Mockito.mock(Entity.class); + Optional> result = CustomEntityWrapper.customModels(entity); + assertTrue(result.isPresent()); + assertTrue(result.get().isEmpty()); + } + + @Test + @DisplayName("Given entity with hook providing models, when getting customModels, then returns those models") + void customModels_returnsModels_whenHookProvidesModels() { + RegistryAccess.registryAccess().registry(RegistryKey.PLUGIN_HOOK) + .register(new TestCustomEntityPluginHook(true, Set.of("mythicmobs:fire_dragon", "mythicmobs:ice_dragon"), false)); + Entity entity = Mockito.mock(Entity.class); + Optional> result = CustomEntityWrapper.customModels(entity); + assertTrue(result.isPresent()); + assertEquals(2, result.get().size()); + assertTrue(result.get().contains("mythicmobs:fire_dragon")); + assertTrue(result.get().contains("mythicmobs:ice_dragon")); + } + } } diff --git a/src/test/java/com/diamonddagger590/mccore/util/item/CustomItemWrapperTest.java b/src/test/java/com/diamonddagger590/mccore/util/item/CustomItemWrapperTest.java index 3d97bd0..d050e64 100644 --- a/src/test/java/com/diamonddagger590/mccore/util/item/CustomItemWrapperTest.java +++ b/src/test/java/com/diamonddagger590/mccore/util/item/CustomItemWrapperTest.java @@ -5,6 +5,7 @@ import com.diamonddagger590.mccore.registry.RegistryAccess; import com.diamonddagger590.mccore.registry.RegistryKey; import com.diamonddagger590.mccore.registry.plugin.PluginHook; +import com.diamonddagger590.mccore.testing.CorePluginTestHelper; import com.diamonddagger590.mccore.testing.RegistryResetExtension; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; @@ -14,6 +15,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockito.Mockito; import java.lang.reflect.Field; import java.util.Optional; @@ -22,18 +25,23 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; class CustomItemWrapperTest { @BeforeEach void setUp() { + MockBukkit.mock(); RegistryResetExtension.setupRegistry(); + CorePluginTestHelper.installMinimalInstance(); } @AfterEach void tearDown() { + CorePluginTestHelper.uninstallInstance(); RegistryResetExtension.resetRegistry(); + MockBukkit.unmock(); } private static CustomItemWrapper materialWrapper(Material material) { @@ -53,18 +61,31 @@ private static CustomItemWrapper customItemWrapper(String customItem) throws Exc static class TestCustomItemPluginHook extends PluginHook implements CustomItemHook { + private final String recognizedItem; + private final ItemStack returnedItem; + private final boolean recognizesItemStack; + private final Set models; + TestCustomItemPluginHook() { + this("nexo:ruby_sword", null, false, Set.of()); + } + + TestCustomItemPluginHook(String recognizedItem, ItemStack returnedItem, boolean recognizesItemStack, Set models) { super(null); + this.recognizedItem = recognizedItem; + this.returnedItem = returnedItem; + this.recognizesItemStack = recognizesItemStack; + this.models = models; } @Override public boolean isItem(@NotNull String itemName) { - return "nexo:ruby_sword".equals(itemName); + return recognizedItem != null && recognizedItem.equals(itemName); } @Override public boolean isItem(@NotNull ItemStack itemStack) { - return false; + return recognizesItemStack; } @Override @@ -75,13 +96,16 @@ public boolean isItemOfType(@NotNull ItemStack itemStack, @NotNull String itemNa @NotNull @Override public Optional item(@NotNull String itemName) { + if (recognizedItem != null && recognizedItem.equals(itemName) && returnedItem != null) { + return Optional.of(returnedItem); + } return Optional.empty(); } @NotNull @Override public Optional> itemModels(@NotNull ItemStack itemStack) { - return Optional.empty(); + return models.isEmpty() ? Optional.empty() : Optional.of(models); } @NotNull @@ -359,4 +383,131 @@ void itemName_returnsHookProvidedName_whenHookIsRegistered() throws Exception { assertEquals("Ruby Sword", wrapper.itemName()); } } + + @Nested + @DisplayName("itemName - vanilla material path") + class ItemNameVanilla { + + @Test + @DisplayName("Given material wrapper, when getting itemName, then returns lang tag with translation key") + void itemName_returnsLangTag_whenMaterialWrapper() { + CustomItemWrapper wrapper = materialWrapper(Material.IRON_INGOT); + String result = wrapper.itemName(); + assertTrue(result.startsWith(""), "Expected lang tag to end with >, got: " + result); + assertTrue(result.contains(Material.IRON_INGOT.translationKey()), "Expected translation key, got: " + result); + } + } + + @Nested + @DisplayName("itemBuilder") + class ItemBuilderTests { + + @Test + @DisplayName("Given material wrapper, when getting itemBuilder, then returns builder with that material") + void itemBuilder_returnsMaterialBuilder_whenMaterialWrapper() { + CustomItemWrapper wrapper = materialWrapper(Material.DIAMOND); + var builder = wrapper.itemBuilder(); + assertNotNull(builder); + } + + @Test + @DisplayName("Given custom item wrapper with no hooks, when getting itemBuilder, then returns AIR builder") + void itemBuilder_returnsAirBuilder_whenNoHooksRegistered() throws Exception { + CustomItemWrapper wrapper = customItemWrapper("nexo:unknown_item"); + var builder = wrapper.itemBuilder(); + assertNotNull(builder); + } + + @Test + @DisplayName("Given custom item wrapper with hook providing item, when getting itemBuilder, then returns hook item builder") + void itemBuilder_returnsHookItemBuilder_whenHookProvidesItem() throws Exception { + RegistryAccess.registryAccess().registry(RegistryKey.PLUGIN_HOOK) + .register(new TestCustomItemPluginHook("nexo:ruby_sword", new ItemStack(Material.DIAMOND_SWORD), false, Set.of())); + CustomItemWrapper wrapper = customItemWrapper("nexo:ruby_sword"); + var builder = wrapper.itemBuilder(); + assertNotNull(builder); + } + + @Test + @DisplayName("Given custom item wrapper with hook not providing item, when getting itemBuilder, then returns AIR builder") + void itemBuilder_returnsAirBuilder_whenHookDoesNotProvideItem() throws Exception { + RegistryAccess.registryAccess().registry(RegistryKey.PLUGIN_HOOK) + .register(new TestCustomItemPluginHook("nexo:other_item", new ItemStack(Material.EMERALD), false, Set.of())); + CustomItemWrapper wrapper = customItemWrapper("nexo:ruby_sword"); + var builder = wrapper.itemBuilder(); + assertNotNull(builder); + } + } + + @Nested + @DisplayName("ItemStack constructor") + class ItemStackConstructor { + + @Test + @DisplayName("Given item stack with hook recognizing it, when constructing wrapper, then sets custom item") + void constructor_setsCustomItem_whenHookRecognizesItemStack() { + RegistryAccess.registryAccess().registry(RegistryKey.PLUGIN_HOOK) + .register(new TestCustomItemPluginHook("nexo:ruby_sword", null, true, Set.of("nexo:ruby_sword"))); + ItemStack itemStack = Mockito.mock(ItemStack.class); + Mockito.when(itemStack.getType()).thenReturn(Material.DIAMOND_SWORD); + + CustomItemWrapper wrapper = new CustomItemWrapper(itemStack); + assertTrue(wrapper.customItem().isPresent()); + assertEquals("nexo:ruby_sword", wrapper.customItem().get()); + assertFalse(wrapper.material().isPresent()); + } + + @Test + @DisplayName("Given item stack with no hooks, when constructing wrapper, then sets material from item type") + void constructor_setsMaterial_whenNoHooksRecognizeItemStack() { + ItemStack itemStack = Mockito.mock(ItemStack.class); + Mockito.when(itemStack.getType()).thenReturn(Material.IRON_INGOT); + + CustomItemWrapper wrapper = new CustomItemWrapper(itemStack); + assertTrue(wrapper.material().isPresent()); + assertEquals(Material.IRON_INGOT, wrapper.material().get()); + assertFalse(wrapper.customItem().isPresent()); + } + + @Test + @DisplayName("Given item stack with hook that recognizes but returns empty models, when constructing wrapper, then sets material") + void constructor_setsMaterial_whenHookReturnsEmptyModels() { + RegistryAccess.registryAccess().registry(RegistryKey.PLUGIN_HOOK) + .register(new TestCustomItemPluginHook("nexo:ruby_sword", null, true, Set.of())); + ItemStack itemStack = Mockito.mock(ItemStack.class); + Mockito.when(itemStack.getType()).thenReturn(Material.DIAMOND_SWORD); + + CustomItemWrapper wrapper = new CustomItemWrapper(itemStack); + assertTrue(wrapper.material().isPresent()); + assertEquals(Material.DIAMOND_SWORD, wrapper.material().get()); + } + } + + @Nested + @DisplayName("static customModels") + class CustomModelsTests { + + @Test + @DisplayName("Given item stack with no hooks registered, when getting customModels, then returns empty set") + void customModels_returnsEmptySet_whenNoHooksRegistered() { + ItemStack itemStack = Mockito.mock(ItemStack.class); + Optional> result = CustomItemWrapper.customModels(itemStack); + assertTrue(result.isPresent()); + assertTrue(result.get().isEmpty()); + } + + @Test + @DisplayName("Given item stack with hook providing models, when getting customModels, then returns those models") + void customModels_returnsModels_whenHookProvidesModels() { + RegistryAccess.registryAccess().registry(RegistryKey.PLUGIN_HOOK) + .register(new TestCustomItemPluginHook("nexo:ruby_sword", null, true, Set.of("nexo:ruby_sword", "nexo:ruby_shield"))); + ItemStack itemStack = Mockito.mock(ItemStack.class); + Optional> result = CustomItemWrapper.customModels(itemStack); + assertTrue(result.isPresent()); + assertEquals(2, result.get().size()); + assertTrue(result.get().contains("nexo:ruby_sword")); + assertTrue(result.get().contains("nexo:ruby_shield")); + } + } }