From bbdf09b2fb0143be49fa455c81cf49b80e66f44f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 14 Jul 2026 09:33:13 +0000 Subject: [PATCH] [Tests] Improve coverage for ItemPluginType, CustomBlockWrapper, CustomItemWrapper, CustomEntityWrapper Add new test class ItemPluginTypeGetCustomItemTest covering all three enum constants' getCustomItem() lambdas (hook present, hook absent, vanilla fallback, invalid name fallback). Enhance CustomBlockWrapperTest with tests for blockName vanilla path, itemBuilder (material/custom with hook/custom without hook), equals(Block), Block constructor, static customModels, drops, and removeBlock. Enhance CustomItemWrapperTest with tests for itemName vanilla path, itemBuilder, ItemStack constructor, and static customModels. Enhance CustomEntityWrapperTest with tests for entityName vanilla path, equals(Entity), Entity constructor, and static customModels. Co-Authored-By: Claude Opus 4.6 Claude-Session: https://claude.ai/code/session_01XBQ6c6i9tMccciPqVZcSiS --- .../item/ItemPluginTypeGetCustomItemTest.java | 319 ++++++++++++++++++ .../util/item/CustomBlockWrapperTest.java | 286 +++++++++++++++- .../util/item/CustomEntityWrapperTest.java | 159 ++++++++- .../util/item/CustomItemWrapperTest.java | 157 ++++++++- 4 files changed, 911 insertions(+), 10 deletions(-) create mode 100644 src/test/java/com/diamonddagger590/mccore/builder/item/ItemPluginTypeGetCustomItemTest.java 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")); + } + } }