From d300c01665168a1e0336ed59bb9c4d4aae5a40f1 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Aug 2026 09:30:01 +0000 Subject: [PATCH] Add branch coverage tests for GUI system and SkullBuilder Improve branch coverage in four areas: - BaseGui: test allowBottomInventoryClick() returning true and empty corePlayerOptional in handleClickEvent - PaginatedGui: test setPage when getMaximumPage() returns 0 - GuiManager: test trackPlayerGui with KeyedGui to cover instanceof branch and CoreGuiOpenEvent key propagation - SkullBuilder: test withAudience(Audience) for both UUID-present and UUID-absent cases, and hideSkullDynamicToolTip with existing tooltip data to cover the merge branch Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01Po6WtTjBc9FBbrYC1e7YSX --- .../builder/item/impl/SkullBuilderTest.java | 54 ++++++++++++ .../mccore/gui/BaseGuiTest.java | 87 +++++++++++++++++++ .../mccore/gui/GuiManagerTest.java | 55 ++++++++++++ .../mccore/gui/PaginatedGuiTest.java | 9 ++ 4 files changed, 205 insertions(+) diff --git a/src/test/java/com/diamonddagger590/mccore/builder/item/impl/SkullBuilderTest.java b/src/test/java/com/diamonddagger590/mccore/builder/item/impl/SkullBuilderTest.java index 7c7913f..6ae1d08 100644 --- a/src/test/java/com/diamonddagger590/mccore/builder/item/impl/SkullBuilderTest.java +++ b/src/test/java/com/diamonddagger590/mccore/builder/item/impl/SkullBuilderTest.java @@ -4,8 +4,13 @@ import com.diamonddagger590.mccore.testing.RegistryResetExtension; import com.diamonddagger590.mccore.testing.TestCorePlugin; import io.papermc.paper.datacomponent.DataComponentTypes; +import io.papermc.paper.datacomponent.item.TooltipDisplay; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.pointer.Pointers; 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; @@ -129,6 +134,36 @@ void build_returnsSelf() { assertSame(builder, result); } + @Test + @DisplayName("Given an Audience with a UUID, when withAudience(Audience) is called, then returns this builder") + void withAudienceAudience_returnsSelf_whenAudienceHasUuid() { + SkullBuilder builder = createBuilder(); + UUID uuid = UUID.randomUUID(); + Audience audience = new Audience() { + @Override + public @NotNull Pointers pointers() { + return Pointers.builder() + .withStatic(Identity.UUID, uuid) + .build(); + } + }; + + SkullBuilder result = builder.withAudience(audience); + + assertSame(builder, result); + } + + @Test + @DisplayName("Given an Audience without a UUID, when withAudience(Audience) is called, then returns this builder") + void withAudienceAudience_returnsSelf_whenAudienceHasNoUuid() { + SkullBuilder builder = createBuilder(); + Audience audience = Audience.empty(); + + SkullBuilder result = builder.withAudience(audience); + + assertSame(builder, result); + } + @Test @DisplayName("Given a built builder, when hideSkullDynamicToolTip is called with no existing tooltip, then tooltip display is set") void hideSkullDynamicToolTip_setsTooltipDisplay_withNoExistingTooltip() { @@ -137,4 +172,23 @@ void hideSkullDynamicToolTip_setsTooltipDisplay_withNoExistingTooltip() { builder.hideSkullDynamicToolTip(); assertTrue(itemStack.hasData(DataComponentTypes.TOOLTIP_DISPLAY)); } + + @Test + @DisplayName("Given an item with existing tooltip display, when hideSkullDynamicToolTip is called, then merges hidden components") + void hideSkullDynamicToolTip_mergesHiddenComponents_withExistingTooltip() { + ItemStack itemStack = new ItemStack(Material.PLAYER_HEAD); + itemStack.setData(DataComponentTypes.TOOLTIP_DISPLAY, + TooltipDisplay.tooltipDisplay() + .addHiddenComponents(DataComponentTypes.MAX_STACK_SIZE) + .build()); + + SkullBuilder builder = new SkullBuilder(itemStack); + builder.hideSkullDynamicToolTip(); + + assertTrue(itemStack.hasData(DataComponentTypes.TOOLTIP_DISPLAY)); + TooltipDisplay tooltip = itemStack.getData(DataComponentTypes.TOOLTIP_DISPLAY); + assertNotNull(tooltip); + assertTrue(tooltip.hiddenComponents().contains(DataComponentTypes.PROFILE)); + assertTrue(tooltip.hiddenComponents().contains(DataComponentTypes.MAX_STACK_SIZE)); + } } diff --git a/src/test/java/com/diamonddagger590/mccore/gui/BaseGuiTest.java b/src/test/java/com/diamonddagger590/mccore/gui/BaseGuiTest.java index 3c6a18f..306c047 100644 --- a/src/test/java/com/diamonddagger590/mccore/gui/BaseGuiTest.java +++ b/src/test/java/com/diamonddagger590/mccore/gui/BaseGuiTest.java @@ -85,6 +85,38 @@ public void unregisterListeners() { } } + private static class BottomClickAllowedGui extends BaseGui { + + private final Inventory mockInventory; + + public BottomClickAllowedGui(@NotNull TestCorePlayer creatingPlayer, @NotNull Inventory mockInventory) { + super(creatingPlayer); + this.mockInventory = mockInventory; + } + + @Override + protected void buildInventory() { + this.inventory = mockInventory; + } + + @Override + public void paintInventory() { + } + + @Override + public void registerListeners() { + } + + @Override + public void unregisterListeners() { + } + + @Override + public boolean allowBottomInventoryClick() { + return true; + } + } + private static class RestrictedSlot implements Slot { private final Set> validTypes; @@ -489,6 +521,61 @@ void handleClickEvent_cancelsEvent_whenBottomInventoryClickedAndNotAllowed() { verify(event).setCancelled(true); } + @SuppressWarnings("unchecked") + @Test + @DisplayName("Given bottom inventory clicked and bottom click allowed, when handleClickEvent, then does not cancel event") + void handleClickEvent_doesNotCancelEvent_whenBottomInventoryClickedAndAllowed() { + BottomClickAllowedGui allowedGui = new BottomClickAllowedGui(player, mockInventory); + allowedGui.getInventory(); + Player mockBukkitPlayer = mock(Player.class); + when(mockBukkitPlayer.getUniqueId()).thenReturn(player.getUUID()); + when(mockGuiManager.getOpenedGui(mockBukkitPlayer)).thenReturn(Optional.of(allowedGui)); + when(mockPlayerManager.getPlayer(player.getUUID())).thenReturn(Optional.of(player)); + + InventoryClickEvent event = mock(InventoryClickEvent.class); + when(event.getSlot()).thenReturn(0); + when(event.getWhoClicked()).thenReturn(mockBukkitPlayer); + + InventoryView mockView = mock(InventoryView.class); + when(event.getView()).thenReturn(mockView); + when(mockView.getTopInventory()).thenReturn(mockInventory); + + Inventory bottomInventory = mock(Inventory.class); + when(mockView.getBottomInventory()).thenReturn(bottomInventory); + when(event.getClickedInventory()).thenReturn(bottomInventory); + + allowedGui.handleClickEvent(event); + + verify(event, never()).setCancelled(true); + } + + @SuppressWarnings("unchecked") + @Test + @DisplayName("Given top inventory clicked and player not found, when handleClickEvent, then does not cancel event") + void handleClickEvent_doesNotCancel_whenPlayerNotFoundInPlayerManager() { + gui.getInventory(); + Player mockBukkitPlayer = mock(Player.class); + when(mockBukkitPlayer.getUniqueId()).thenReturn(player.getUUID()); + when(mockGuiManager.getOpenedGui(mockBukkitPlayer)).thenReturn(Optional.of(gui)); + when(mockPlayerManager.getPlayer(player.getUUID())).thenReturn(Optional.empty()); + + InventoryClickEvent event = mock(InventoryClickEvent.class); + when(event.getSlot()).thenReturn(0); + when(event.getClick()).thenReturn(ClickType.LEFT); + when(event.getWhoClicked()).thenReturn(mockBukkitPlayer); + + InventoryView mockView = mock(InventoryView.class); + when(event.getView()).thenReturn(mockView); + when(mockView.getTopInventory()).thenReturn(mockInventory); + when(mockView.getBottomInventory()).thenReturn(mock(Inventory.class)); + when(event.getClickedInventory()).thenReturn(mockInventory); + + gui.handleClickEvent(event); + + verify(event, never()).setCancelled(true); + verify(event, never()).setCancelled(false); + } + @SuppressWarnings("unchecked") @Test @DisplayName("Given canProcessEvent returns false, when handleClickEvent, then does not cancel event") diff --git a/src/test/java/com/diamonddagger590/mccore/gui/GuiManagerTest.java b/src/test/java/com/diamonddagger590/mccore/gui/GuiManagerTest.java index 9c4f272..e9b9150 100644 --- a/src/test/java/com/diamonddagger590/mccore/gui/GuiManagerTest.java +++ b/src/test/java/com/diamonddagger590/mccore/gui/GuiManagerTest.java @@ -1,8 +1,10 @@ package com.diamonddagger590.mccore.gui; import com.diamonddagger590.mccore.CorePlugin; +import com.diamonddagger590.mccore.event.gui.CoreGuiOpenEvent; import com.diamonddagger590.mccore.player.CorePlayer; import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; import org.bukkit.Server; import org.bukkit.entity.Player; import org.bukkit.plugin.PluginManager; @@ -10,6 +12,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; import java.lang.reflect.Field; import java.util.Optional; @@ -330,4 +333,56 @@ void trackPlayerGui_firesEvent_always() { verify(mockPluginManager).callEvent(any()); } + + @SuppressWarnings("unchecked") + @Test + @DisplayName("Given a KeyedGui with a key, when trackPlayerGui, then CoreGuiOpenEvent carries the key") + void trackPlayerGui_firesEventWithKey_whenGuiIsKeyed() { + UUID playerUUID = UUID.randomUUID(); + NamespacedKey expectedKey = new NamespacedKey("testplugin", "test_gui"); + + Gui gui = mock(Gui.class, org.mockito.Mockito.withSettings().extraInterfaces(KeyedGui.class)); + when(gui.getUUID()).thenReturn(UUID.randomUUID()); + when(((KeyedGui) gui).getGuiKey()).thenReturn(Optional.of(expectedKey)); + + guiManager.trackPlayerGui(playerUUID, gui); + + ArgumentCaptor captor = ArgumentCaptor.forClass(CoreGuiOpenEvent.class); + verify(mockPluginManager).callEvent(captor.capture()); + CoreGuiOpenEvent event = captor.getValue(); + assertTrue(event.getGuiKey().isPresent()); + assertEquals(expectedKey, event.getGuiKey().get()); + } + + @SuppressWarnings("unchecked") + @Test + @DisplayName("Given a KeyedGui with empty key, when trackPlayerGui, then CoreGuiOpenEvent has empty key") + void trackPlayerGui_firesEventWithEmptyKey_whenKeyedGuiReturnsEmpty() { + UUID playerUUID = UUID.randomUUID(); + + Gui gui = mock(Gui.class, org.mockito.Mockito.withSettings().extraInterfaces(KeyedGui.class)); + when(gui.getUUID()).thenReturn(UUID.randomUUID()); + when(((KeyedGui) gui).getGuiKey()).thenReturn(Optional.empty()); + + guiManager.trackPlayerGui(playerUUID, gui); + + ArgumentCaptor captor = ArgumentCaptor.forClass(CoreGuiOpenEvent.class); + verify(mockPluginManager).callEvent(captor.capture()); + CoreGuiOpenEvent event = captor.getValue(); + assertFalse(event.getGuiKey().isPresent()); + } + + @Test + @DisplayName("Given a non-keyed gui, when trackPlayerGui, then CoreGuiOpenEvent has empty key") + void trackPlayerGui_firesEventWithNullKey_whenGuiIsNotKeyed() { + UUID playerUUID = UUID.randomUUID(); + Gui gui = createMockGui(); + + guiManager.trackPlayerGui(playerUUID, gui); + + ArgumentCaptor captor = ArgumentCaptor.forClass(CoreGuiOpenEvent.class); + verify(mockPluginManager).callEvent(captor.capture()); + CoreGuiOpenEvent event = captor.getValue(); + assertFalse(event.getGuiKey().isPresent()); + } } diff --git a/src/test/java/com/diamonddagger590/mccore/gui/PaginatedGuiTest.java b/src/test/java/com/diamonddagger590/mccore/gui/PaginatedGuiTest.java index dd9c74f..44a6528 100644 --- a/src/test/java/com/diamonddagger590/mccore/gui/PaginatedGuiTest.java +++ b/src/test/java/com/diamonddagger590/mccore/gui/PaginatedGuiTest.java @@ -189,6 +189,15 @@ void setPage_throwsForNegativePage() { assertThrows(IllegalArgumentException.class, () -> gui.setPage(-1)); } + + @Test + @DisplayName("Given maximum page is 0, when setPage to 1, then throws IllegalArgumentException") + void setPage_throwsForMaxPageLessThanOne() { + TestPaginatedGui gui = new TestPaginatedGui(player, mockInventory, 0); + gui.getInventory(); + + assertThrows(IllegalArgumentException.class, () -> gui.setPage(1)); + } } @Nested